claude-mpm 5.1.8__py3-none-any.whl → 5.4.22__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 (191) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/__init__.py +4 -0
  3. claude_mpm/agents/{PM_INSTRUCTIONS_TEACH.md → CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md} +721 -41
  4. claude_mpm/agents/PM_INSTRUCTIONS.md +290 -34
  5. claude_mpm/agents/agent_loader.py +13 -44
  6. claude_mpm/agents/frontmatter_validator.py +68 -0
  7. claude_mpm/agents/templates/circuit-breakers.md +138 -1
  8. claude_mpm/cli/__main__.py +4 -0
  9. claude_mpm/cli/chrome_devtools_installer.py +175 -0
  10. claude_mpm/cli/commands/agent_state_manager.py +8 -17
  11. claude_mpm/cli/commands/agents.py +169 -31
  12. claude_mpm/cli/commands/auto_configure.py +210 -25
  13. claude_mpm/cli/commands/config.py +88 -2
  14. claude_mpm/cli/commands/configure.py +1111 -161
  15. claude_mpm/cli/commands/configure_agent_display.py +15 -6
  16. claude_mpm/cli/commands/mpm_init/core.py +160 -46
  17. claude_mpm/cli/commands/mpm_init/knowledge_extractor.py +481 -0
  18. claude_mpm/cli/commands/mpm_init/prompts.py +280 -0
  19. claude_mpm/cli/commands/skills.py +214 -189
  20. claude_mpm/cli/commands/summarize.py +413 -0
  21. claude_mpm/cli/executor.py +11 -3
  22. claude_mpm/cli/parsers/agents_parser.py +54 -9
  23. claude_mpm/cli/parsers/auto_configure_parser.py +0 -138
  24. claude_mpm/cli/parsers/base_parser.py +5 -0
  25. claude_mpm/cli/parsers/config_parser.py +153 -83
  26. claude_mpm/cli/parsers/skills_parser.py +3 -2
  27. claude_mpm/cli/startup.py +550 -94
  28. claude_mpm/commands/mpm-config.md +265 -0
  29. claude_mpm/commands/mpm-help.md +14 -95
  30. claude_mpm/commands/mpm-organize.md +500 -0
  31. claude_mpm/config/agent_sources.py +27 -0
  32. claude_mpm/core/framework/formatters/content_formatter.py +3 -13
  33. claude_mpm/core/framework/loaders/agent_loader.py +8 -5
  34. claude_mpm/core/framework_loader.py +4 -2
  35. claude_mpm/core/logger.py +13 -0
  36. claude_mpm/core/output_style_manager.py +173 -43
  37. claude_mpm/core/socketio_pool.py +3 -3
  38. claude_mpm/core/unified_agent_registry.py +134 -16
  39. claude_mpm/hooks/claude_hooks/correlation_manager.py +60 -0
  40. claude_mpm/hooks/claude_hooks/event_handlers.py +211 -78
  41. claude_mpm/hooks/claude_hooks/hook_handler.py +6 -0
  42. claude_mpm/hooks/claude_hooks/installer.py +33 -10
  43. claude_mpm/hooks/claude_hooks/memory_integration.py +26 -9
  44. claude_mpm/hooks/claude_hooks/response_tracking.py +2 -3
  45. claude_mpm/hooks/claude_hooks/services/connection_manager.py +4 -0
  46. claude_mpm/hooks/memory_integration_hook.py +46 -1
  47. claude_mpm/init.py +0 -19
  48. claude_mpm/models/agent_definition.py +7 -0
  49. claude_mpm/scripts/claude-hook-handler.sh +58 -18
  50. claude_mpm/scripts/launch_monitor.py +93 -13
  51. claude_mpm/scripts/start_activity_logging.py +0 -0
  52. claude_mpm/services/agents/agent_recommendation_service.py +278 -0
  53. claude_mpm/services/agents/agent_review_service.py +280 -0
  54. claude_mpm/services/agents/deployment/agent_discovery_service.py +2 -3
  55. claude_mpm/services/agents/deployment/agent_template_builder.py +4 -2
  56. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +188 -12
  57. claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +531 -55
  58. claude_mpm/services/agents/git_source_manager.py +34 -0
  59. claude_mpm/services/agents/loading/base_agent_manager.py +1 -13
  60. claude_mpm/services/agents/sources/git_source_sync_service.py +8 -1
  61. claude_mpm/services/agents/toolchain_detector.py +10 -6
  62. claude_mpm/services/analysis/__init__.py +11 -1
  63. claude_mpm/services/analysis/clone_detector.py +1030 -0
  64. claude_mpm/services/command_deployment_service.py +81 -10
  65. claude_mpm/services/event_bus/config.py +3 -1
  66. claude_mpm/services/git/git_operations_service.py +93 -8
  67. claude_mpm/services/monitor/daemon.py +9 -2
  68. claude_mpm/services/monitor/daemon_manager.py +39 -3
  69. claude_mpm/services/monitor/server.py +225 -19
  70. claude_mpm/services/self_upgrade_service.py +120 -12
  71. claude_mpm/services/skills/__init__.py +3 -0
  72. claude_mpm/services/skills/git_skill_source_manager.py +32 -2
  73. claude_mpm/services/skills/selective_skill_deployer.py +704 -0
  74. claude_mpm/services/skills/skill_to_agent_mapper.py +406 -0
  75. claude_mpm/services/skills_deployer.py +126 -9
  76. claude_mpm/services/socketio/event_normalizer.py +15 -1
  77. claude_mpm/services/socketio/server/core.py +160 -21
  78. claude_mpm/services/version_control/git_operations.py +103 -0
  79. claude_mpm/utils/agent_filters.py +17 -44
  80. {claude_mpm-5.1.8.dist-info → claude_mpm-5.4.22.dist-info}/METADATA +47 -84
  81. {claude_mpm-5.1.8.dist-info → claude_mpm-5.4.22.dist-info}/RECORD +86 -176
  82. claude_mpm-5.4.22.dist-info/entry_points.txt +5 -0
  83. claude_mpm-5.4.22.dist-info/licenses/LICENSE +94 -0
  84. claude_mpm-5.4.22.dist-info/licenses/LICENSE-FAQ.md +153 -0
  85. claude_mpm/agents/BASE_AGENT_TEMPLATE.md +0 -292
  86. claude_mpm/agents/BASE_DOCUMENTATION.md +0 -53
  87. claude_mpm/agents/BASE_ENGINEER.md +0 -658
  88. claude_mpm/agents/BASE_OPS.md +0 -219
  89. claude_mpm/agents/BASE_PM.md +0 -480
  90. claude_mpm/agents/BASE_PROMPT_ENGINEER.md +0 -787
  91. claude_mpm/agents/BASE_QA.md +0 -167
  92. claude_mpm/agents/BASE_RESEARCH.md +0 -53
  93. claude_mpm/agents/base_agent.json +0 -31
  94. claude_mpm/agents/base_agent_loader.py +0 -601
  95. claude_mpm/cli/commands/agents_detect.py +0 -380
  96. claude_mpm/cli/commands/agents_recommend.py +0 -309
  97. claude_mpm/cli/ticket_cli.py +0 -35
  98. claude_mpm/commands/mpm-agents-auto-configure.md +0 -278
  99. claude_mpm/commands/mpm-agents-detect.md +0 -177
  100. claude_mpm/commands/mpm-agents-list.md +0 -131
  101. claude_mpm/commands/mpm-agents-recommend.md +0 -223
  102. claude_mpm/commands/mpm-config-view.md +0 -150
  103. claude_mpm/commands/mpm-ticket-organize.md +0 -304
  104. claude_mpm/dashboard/analysis_runner.py +0 -455
  105. claude_mpm/dashboard/index.html +0 -13
  106. claude_mpm/dashboard/open_dashboard.py +0 -66
  107. claude_mpm/dashboard/static/css/activity.css +0 -1958
  108. claude_mpm/dashboard/static/css/connection-status.css +0 -370
  109. claude_mpm/dashboard/static/css/dashboard.css +0 -4701
  110. claude_mpm/dashboard/static/js/components/activity-tree.js +0 -1871
  111. claude_mpm/dashboard/static/js/components/agent-hierarchy.js +0 -777
  112. claude_mpm/dashboard/static/js/components/agent-inference.js +0 -956
  113. claude_mpm/dashboard/static/js/components/build-tracker.js +0 -333
  114. claude_mpm/dashboard/static/js/components/code-simple.js +0 -857
  115. claude_mpm/dashboard/static/js/components/connection-debug.js +0 -654
  116. claude_mpm/dashboard/static/js/components/diff-viewer.js +0 -891
  117. claude_mpm/dashboard/static/js/components/event-processor.js +0 -542
  118. claude_mpm/dashboard/static/js/components/event-viewer.js +0 -1155
  119. claude_mpm/dashboard/static/js/components/export-manager.js +0 -368
  120. claude_mpm/dashboard/static/js/components/file-change-tracker.js +0 -443
  121. claude_mpm/dashboard/static/js/components/file-change-viewer.js +0 -690
  122. claude_mpm/dashboard/static/js/components/file-tool-tracker.js +0 -724
  123. claude_mpm/dashboard/static/js/components/file-viewer.js +0 -580
  124. claude_mpm/dashboard/static/js/components/hud-library-loader.js +0 -211
  125. claude_mpm/dashboard/static/js/components/hud-manager.js +0 -671
  126. claude_mpm/dashboard/static/js/components/hud-visualizer.js +0 -1718
  127. claude_mpm/dashboard/static/js/components/module-viewer.js +0 -2764
  128. claude_mpm/dashboard/static/js/components/session-manager.js +0 -579
  129. claude_mpm/dashboard/static/js/components/socket-manager.js +0 -368
  130. claude_mpm/dashboard/static/js/components/ui-state-manager.js +0 -749
  131. claude_mpm/dashboard/static/js/components/unified-data-viewer.js +0 -1824
  132. claude_mpm/dashboard/static/js/components/working-directory.js +0 -920
  133. claude_mpm/dashboard/static/js/connection-manager.js +0 -536
  134. claude_mpm/dashboard/static/js/dashboard.js +0 -1914
  135. claude_mpm/dashboard/static/js/extension-error-handler.js +0 -164
  136. claude_mpm/dashboard/static/js/socket-client.js +0 -1474
  137. claude_mpm/dashboard/static/js/tab-isolation-fix.js +0 -185
  138. claude_mpm/dashboard/static/socket.io.min.js +0 -7
  139. claude_mpm/dashboard/static/socket.io.v4.8.1.backup.js +0 -7
  140. claude_mpm/dashboard/templates/code_simple.html +0 -153
  141. claude_mpm/dashboard/templates/index.html +0 -606
  142. claude_mpm/dashboard/test_dashboard.html +0 -372
  143. claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-313.pyc +0 -0
  144. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-313.pyc +0 -0
  145. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-313.pyc +0 -0
  146. claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-313.pyc +0 -0
  147. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-313.pyc +0 -0
  148. claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-313.pyc +0 -0
  149. claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-313.pyc +0 -0
  150. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-313.pyc +0 -0
  151. claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-313.pyc +0 -0
  152. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-313.pyc +0 -0
  153. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-313.pyc +0 -0
  154. claude_mpm/scripts/mcp_server.py +0 -75
  155. claude_mpm/scripts/mcp_wrapper.py +0 -39
  156. claude_mpm/services/mcp_gateway/__init__.py +0 -159
  157. claude_mpm/services/mcp_gateway/auto_configure.py +0 -369
  158. claude_mpm/services/mcp_gateway/config/__init__.py +0 -17
  159. claude_mpm/services/mcp_gateway/config/config_loader.py +0 -296
  160. claude_mpm/services/mcp_gateway/config/config_schema.py +0 -243
  161. claude_mpm/services/mcp_gateway/config/configuration.py +0 -429
  162. claude_mpm/services/mcp_gateway/core/__init__.py +0 -43
  163. claude_mpm/services/mcp_gateway/core/base.py +0 -312
  164. claude_mpm/services/mcp_gateway/core/exceptions.py +0 -253
  165. claude_mpm/services/mcp_gateway/core/interfaces.py +0 -443
  166. claude_mpm/services/mcp_gateway/core/process_pool.py +0 -977
  167. claude_mpm/services/mcp_gateway/core/singleton_manager.py +0 -315
  168. claude_mpm/services/mcp_gateway/core/startup_verification.py +0 -316
  169. claude_mpm/services/mcp_gateway/main.py +0 -589
  170. claude_mpm/services/mcp_gateway/registry/__init__.py +0 -12
  171. claude_mpm/services/mcp_gateway/registry/service_registry.py +0 -412
  172. claude_mpm/services/mcp_gateway/registry/tool_registry.py +0 -489
  173. claude_mpm/services/mcp_gateway/server/__init__.py +0 -15
  174. claude_mpm/services/mcp_gateway/server/mcp_gateway.py +0 -414
  175. claude_mpm/services/mcp_gateway/server/stdio_handler.py +0 -372
  176. claude_mpm/services/mcp_gateway/server/stdio_server.py +0 -712
  177. claude_mpm/services/mcp_gateway/tools/__init__.py +0 -36
  178. claude_mpm/services/mcp_gateway/tools/base_adapter.py +0 -485
  179. claude_mpm/services/mcp_gateway/tools/document_summarizer.py +0 -789
  180. claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +0 -654
  181. claude_mpm/services/mcp_gateway/tools/health_check_tool.py +0 -456
  182. claude_mpm/services/mcp_gateway/tools/hello_world.py +0 -551
  183. claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +0 -555
  184. claude_mpm/services/mcp_gateway/utils/__init__.py +0 -14
  185. claude_mpm/services/mcp_gateway/utils/package_version_checker.py +0 -160
  186. claude_mpm/services/mcp_gateway/utils/update_preferences.py +0 -170
  187. claude_mpm-5.1.8.dist-info/entry_points.txt +0 -10
  188. claude_mpm-5.1.8.dist-info/licenses/LICENSE +0 -21
  189. /claude_mpm/agents/{OUTPUT_STYLE.md → CLAUDE_MPM_OUTPUT_STYLE.md} +0 -0
  190. {claude_mpm-5.1.8.dist-info → claude_mpm-5.4.22.dist-info}/WHEEL +0 -0
  191. {claude_mpm-5.1.8.dist-info → claude_mpm-5.4.22.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
@@ -3,7 +3,7 @@ Self-Upgrade Service
3
3
  ====================
4
4
 
5
5
  Handles version checking and self-upgrade functionality for claude-mpm.
6
- Supports pip, pipx, and npm installations with automatic detection.
6
+ Supports pip, pipx, npm, uv tool, and Homebrew installations with automatic detection.
7
7
  Also checks Claude Code version compatibility.
8
8
 
9
9
  WHY: Users should be notified of updates and have an easy way to upgrade
@@ -11,7 +11,7 @@ without manually running installation commands. Claude Code version checking
11
11
  ensures compatibility with required features.
12
12
 
13
13
  DESIGN DECISIONS:
14
- - Detects installation method (pip/pipx/npm/editable)
14
+ - Detects installation method (pip/pipx/npm/uv_tool/homebrew/editable)
15
15
  - Non-blocking version checks with caching
16
16
  - Interactive upgrade prompts with confirmation
17
17
  - Automatic restart after upgrade
@@ -40,6 +40,8 @@ class InstallationMethod:
40
40
  PIP = "pip"
41
41
  PIPX = "pipx"
42
42
  NPM = "npm"
43
+ UV_TOOL = "uv_tool"
44
+ HOMEBREW = "homebrew"
43
45
  EDITABLE = "editable"
44
46
  UNKNOWN = "unknown"
45
47
 
@@ -95,6 +97,14 @@ class SelfUpgradeService:
95
97
  """
96
98
  Detect how claude-mpm was installed.
97
99
 
100
+ Detection priority:
101
+ 1. Editable (skip auto-upgrade)
102
+ 2. UV Tool
103
+ 3. Homebrew
104
+ 4. Pipx
105
+ 5. NPM
106
+ 6. Pip (default)
107
+
98
108
  Returns:
99
109
  Installation method constant
100
110
  """
@@ -105,6 +115,14 @@ class SelfUpgradeService:
105
115
  ]:
106
116
  return InstallationMethod.EDITABLE
107
117
 
118
+ # Check for UV tool installation
119
+ if self._check_uv_tool_installation():
120
+ return InstallationMethod.UV_TOOL
121
+
122
+ # Check for Homebrew installation
123
+ if self._check_homebrew_installation():
124
+ return InstallationMethod.HOMEBREW
125
+
108
126
  # Check for pipx by looking at executable path
109
127
  executable = sys.executable
110
128
  if "pipx" in executable:
@@ -127,6 +145,85 @@ class SelfUpgradeService:
127
145
  # Default to pip
128
146
  return InstallationMethod.PIP
129
147
 
148
+ def _check_uv_tool_installation(self) -> bool:
149
+ """
150
+ Check if claude-mpm is installed via uv tool.
151
+
152
+ Detection methods:
153
+ 1. Check UV_TOOL_DIR environment variable
154
+ 2. Check if executable path contains .local/share/uv/tools/
155
+ 3. Fallback: Run `uv tool list` and check for claude-mpm
156
+
157
+ Returns:
158
+ True if UV tool installation detected
159
+ """
160
+ # Method 1: Check UV_TOOL_DIR environment variable
161
+ uv_tool_dir = os.environ.get("UV_TOOL_DIR")
162
+ if uv_tool_dir and "claude-mpm" in uv_tool_dir:
163
+ return True
164
+
165
+ # Method 2: Check executable path
166
+ executable = sys.executable
167
+ if ".local/share/uv/tools/" in executable or "uv/tools/" in executable:
168
+ return True
169
+
170
+ # Method 3: Fallback to `uv tool list` command
171
+ try:
172
+ result = subprocess.run(
173
+ ["uv", "tool", "list"],
174
+ check=False,
175
+ capture_output=True,
176
+ text=True,
177
+ timeout=5,
178
+ )
179
+ if result.returncode == 0 and "claude-mpm" in result.stdout:
180
+ return True
181
+ except (FileNotFoundError, subprocess.TimeoutExpired):
182
+ # uv not installed or command failed
183
+ pass
184
+ except Exception as e:
185
+ self.logger.debug(f"UV tool check failed: {e}")
186
+
187
+ return False
188
+
189
+ def _check_homebrew_installation(self) -> bool:
190
+ """
191
+ Check if claude-mpm is installed via Homebrew.
192
+
193
+ Detection methods:
194
+ 1. Check if executable path starts with /opt/homebrew/ or /usr/local/Cellar/
195
+ 2. Fallback: Run `brew list claude-mpm` to verify
196
+
197
+ Returns:
198
+ True if Homebrew installation detected
199
+ """
200
+ # Method 1: Check executable path
201
+ executable = sys.executable
202
+ if executable.startswith("/opt/homebrew/") or executable.startswith(
203
+ "/usr/local/Cellar/"
204
+ ):
205
+ return True
206
+
207
+ # Method 2: Fallback to `brew list` command
208
+ try:
209
+ result = subprocess.run(
210
+ ["brew", "list", "claude-mpm"],
211
+ check=False,
212
+ capture_output=True,
213
+ text=True,
214
+ timeout=5,
215
+ )
216
+ # brew list returns 0 if package is installed
217
+ if result.returncode == 0:
218
+ return True
219
+ except (FileNotFoundError, subprocess.TimeoutExpired):
220
+ # brew not installed or command failed
221
+ pass
222
+ except Exception as e:
223
+ self.logger.debug(f"Homebrew check failed: {e}")
224
+
225
+ return False
226
+
130
227
  def _get_claude_code_version(self) -> Optional[str]:
131
228
  """
132
229
  Get the installed Claude Code version.
@@ -257,6 +354,8 @@ class SelfUpgradeService:
257
354
  if self.installation_method in [
258
355
  InstallationMethod.PIP,
259
356
  InstallationMethod.PIPX,
357
+ InstallationMethod.UV_TOOL,
358
+ InstallationMethod.HOMEBREW,
260
359
  ]:
261
360
  result = await self.version_checker.check_for_update(
262
361
  "claude-mpm", self.current_version, cache_ttl
@@ -313,15 +412,18 @@ class SelfUpgradeService:
313
412
  Returns:
314
413
  Shell command string to upgrade claude-mpm
315
414
  """
316
- if self.installation_method == InstallationMethod.PIPX:
317
- return "pipx upgrade claude-mpm"
318
- if self.installation_method == InstallationMethod.NPM:
319
- return "npm update -g claude-mpm"
320
- if self.installation_method == InstallationMethod.PIP:
321
- return f"{sys.executable} -m pip install --upgrade claude-mpm"
322
- if self.installation_method == InstallationMethod.EDITABLE:
323
- return "git pull && pip install -e ."
324
- return "pip install --upgrade claude-mpm"
415
+ upgrade_commands = {
416
+ InstallationMethod.UV_TOOL: "uv tool upgrade claude-mpm",
417
+ InstallationMethod.HOMEBREW: "brew upgrade claude-mpm",
418
+ InstallationMethod.PIPX: "pipx upgrade claude-mpm",
419
+ InstallationMethod.NPM: "npm update -g claude-mpm",
420
+ InstallationMethod.PIP: f"{sys.executable} -m pip install --upgrade claude-mpm",
421
+ InstallationMethod.EDITABLE: "git pull && pip install -e .",
422
+ }
423
+
424
+ return upgrade_commands.get(
425
+ self.installation_method, "pip install --upgrade claude-mpm"
426
+ )
325
427
 
326
428
  def prompt_for_upgrade(self, update_info: Dict[str, any]) -> bool:
327
429
  """
@@ -432,7 +534,13 @@ class SelfUpgradeService:
432
534
  args = sys.argv[:]
433
535
 
434
536
  # Replace current process with new one
435
- if self.installation_method == InstallationMethod.PIPX:
537
+ if self.installation_method == InstallationMethod.UV_TOOL:
538
+ # Use uv run
539
+ os.execvp("uv", ["uv", "tool", "run", "claude-mpm", *args[1:]])
540
+ elif self.installation_method == InstallationMethod.HOMEBREW:
541
+ # Use direct executable (installed to PATH by Homebrew)
542
+ os.execvp("claude-mpm", args)
543
+ elif self.installation_method == InstallationMethod.PIPX:
436
544
  # Use pipx run
437
545
  os.execvp("pipx", ["pipx", "run", "claude-mpm", *args[1:]])
438
546
  elif self.installation_method == InstallationMethod.NPM:
@@ -3,6 +3,7 @@
3
3
  This package provides services for Git-based skills management:
4
4
  - GitSkillSourceManager: Multi-repository orchestration with priority resolution
5
5
  - SkillDiscoveryService: Parse skills from Git repositories (Markdown with YAML frontmatter)
6
+ - SkillToAgentMapper: Map skills to agents using YAML configuration
6
7
  """
7
8
 
8
9
  from claude_mpm.services.skills.git_skill_source_manager import GitSkillSourceManager
@@ -10,9 +11,11 @@ from claude_mpm.services.skills.skill_discovery_service import (
10
11
  SkillDiscoveryService,
11
12
  SkillMetadata,
12
13
  )
14
+ from claude_mpm.services.skills.skill_to_agent_mapper import SkillToAgentMapper
13
15
 
14
16
  __all__ = [
15
17
  "GitSkillSourceManager",
16
18
  "SkillDiscoveryService",
17
19
  "SkillMetadata",
20
+ "SkillToAgentMapper",
18
21
  ]
@@ -20,7 +20,7 @@ from concurrent.futures import ThreadPoolExecutor, as_completed
20
20
  from datetime import datetime, timezone
21
21
  from pathlib import Path
22
22
  from threading import Lock
23
- from typing import Any, Dict, List, Optional, Tuple
23
+ from typing import Any, Dict, List, Optional, Set, Tuple
24
24
 
25
25
  from claude_mpm.config.skill_sources import SkillSource, SkillSourceConfiguration
26
26
  from claude_mpm.core.logging_config import get_logger
@@ -989,6 +989,7 @@ class GitSkillSourceManager:
989
989
  target_dir: Optional[Path] = None,
990
990
  force: bool = False,
991
991
  progress_callback=None,
992
+ skill_filter: Optional[Set[str]] = None,
992
993
  ) -> Dict[str, Any]:
993
994
  """Deploy skills from cache to target directory with flat structure.
994
995
 
@@ -1004,6 +1005,9 @@ class GitSkillSourceManager:
1004
1005
  target_dir: Target deployment directory (default: ~/.claude/skills/)
1005
1006
  force: Overwrite existing skills
1006
1007
  progress_callback: Optional callback(increment: int) called for each skill deployed
1008
+ skill_filter: Optional set of skill names to deploy (selective deployment).
1009
+ If None, deploys all skills. If provided, only deploys skills
1010
+ whose name matches an entry in the filter set.
1007
1011
 
1008
1012
  Returns:
1009
1013
  Dict with deployment results:
@@ -1013,13 +1017,19 @@ class GitSkillSourceManager:
1013
1017
  "failed_count": int,
1014
1018
  "deployed_skills": List[str],
1015
1019
  "skipped_skills": List[str],
1016
- "errors": List[str]
1020
+ "errors": List[str],
1021
+ "filtered_count": int # Number of skills filtered out
1017
1022
  }
1018
1023
 
1019
1024
  Example:
1020
1025
  >>> manager = GitSkillSourceManager(config)
1021
1026
  >>> result = manager.deploy_skills()
1022
1027
  >>> print(f"Deployed {result['deployed_count']} skills")
1028
+
1029
+ # Selective deployment based on agent requirements:
1030
+ >>> required = {"typescript-core", "react-patterns"}
1031
+ >>> result = manager.deploy_skills(skill_filter=required)
1032
+ >>> print(f"Deployed {result['deployed_count']} of {len(required)} required skills")
1023
1033
  """
1024
1034
  if target_dir is None:
1025
1035
  target_dir = Path.home() / ".claude" / "skills"
@@ -1029,10 +1039,29 @@ class GitSkillSourceManager:
1029
1039
  deployed = []
1030
1040
  skipped = []
1031
1041
  errors = []
1042
+ filtered_count = 0
1032
1043
 
1033
1044
  # Get all skills from all sources
1034
1045
  all_skills = self.get_all_skills()
1035
1046
 
1047
+ # Apply skill filter if provided (selective deployment)
1048
+ if skill_filter is not None:
1049
+ original_count = len(all_skills)
1050
+ # Normalize filter to lowercase for case-insensitive matching
1051
+ normalized_filter = {s.lower() for s in skill_filter}
1052
+ # Match against deployment_name (not display name) since skill_filter contains
1053
+ # deployment-style names like "toolchains-python-frameworks-django"
1054
+ all_skills = [
1055
+ s
1056
+ for s in all_skills
1057
+ if s.get("deployment_name", "").lower() in normalized_filter
1058
+ ]
1059
+ filtered_count = original_count - len(all_skills)
1060
+ self.logger.info(
1061
+ f"Selective deployment: {len(all_skills)} of {original_count} skills "
1062
+ f"match agent requirements ({filtered_count} filtered out)"
1063
+ )
1064
+
1036
1065
  self.logger.info(
1037
1066
  f"Deploying {len(all_skills)} skills to {target_dir} (force={force})"
1038
1067
  )
@@ -1083,6 +1112,7 @@ class GitSkillSourceManager:
1083
1112
  "deployed_skills": deployed,
1084
1113
  "skipped_skills": skipped,
1085
1114
  "errors": errors,
1115
+ "filtered_count": filtered_count,
1086
1116
  }
1087
1117
 
1088
1118
  def _deploy_single_skill(