claude-mpm 5.1.9__py3-none-any.whl → 5.4.14__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 (162) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/__init__.py +4 -0
  3. claude_mpm/agents/PM_INSTRUCTIONS.md +85 -0
  4. claude_mpm/agents/agent_loader.py +13 -44
  5. claude_mpm/agents/templates/circuit-breakers.md +138 -1
  6. claude_mpm/cli/__main__.py +4 -0
  7. claude_mpm/cli/commands/agent_state_manager.py +8 -17
  8. claude_mpm/cli/commands/auto_configure.py +210 -25
  9. claude_mpm/cli/commands/config.py +88 -2
  10. claude_mpm/cli/commands/configure.py +1097 -158
  11. claude_mpm/cli/commands/configure_agent_display.py +15 -6
  12. claude_mpm/cli/commands/mpm_init/core.py +160 -46
  13. claude_mpm/cli/commands/mpm_init/knowledge_extractor.py +481 -0
  14. claude_mpm/cli/commands/mpm_init/prompts.py +280 -0
  15. claude_mpm/cli/commands/skills.py +21 -2
  16. claude_mpm/cli/commands/summarize.py +413 -0
  17. claude_mpm/cli/executor.py +11 -3
  18. claude_mpm/cli/parsers/base_parser.py +5 -0
  19. claude_mpm/cli/parsers/config_parser.py +153 -83
  20. claude_mpm/cli/parsers/skills_parser.py +3 -2
  21. claude_mpm/cli/startup.py +333 -89
  22. claude_mpm/commands/mpm-config.md +266 -0
  23. claude_mpm/commands/{mpm-ticket-organize.md → mpm-organize.md} +4 -5
  24. claude_mpm/config/agent_sources.py +27 -0
  25. claude_mpm/core/framework/formatters/content_formatter.py +3 -13
  26. claude_mpm/core/framework/loaders/agent_loader.py +8 -5
  27. claude_mpm/core/framework_loader.py +4 -2
  28. claude_mpm/core/logger.py +13 -0
  29. claude_mpm/core/socketio_pool.py +3 -3
  30. claude_mpm/core/unified_agent_registry.py +5 -15
  31. claude_mpm/hooks/claude_hooks/correlation_manager.py +60 -0
  32. claude_mpm/hooks/claude_hooks/event_handlers.py +206 -78
  33. claude_mpm/hooks/claude_hooks/hook_handler.py +6 -0
  34. claude_mpm/hooks/claude_hooks/installer.py +33 -10
  35. claude_mpm/hooks/claude_hooks/memory_integration.py +26 -9
  36. claude_mpm/hooks/claude_hooks/response_tracking.py +2 -3
  37. claude_mpm/hooks/claude_hooks/services/connection_manager.py +4 -0
  38. claude_mpm/hooks/memory_integration_hook.py +46 -1
  39. claude_mpm/init.py +0 -19
  40. claude_mpm/scripts/claude-hook-handler.sh +58 -18
  41. claude_mpm/scripts/launch_monitor.py +93 -13
  42. claude_mpm/services/agents/agent_recommendation_service.py +278 -0
  43. claude_mpm/services/agents/agent_review_service.py +280 -0
  44. claude_mpm/services/agents/deployment/agent_discovery_service.py +2 -3
  45. claude_mpm/services/agents/deployment/agent_template_builder.py +4 -2
  46. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +78 -9
  47. claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +335 -53
  48. claude_mpm/services/agents/git_source_manager.py +34 -0
  49. claude_mpm/services/agents/loading/base_agent_manager.py +1 -13
  50. claude_mpm/services/agents/sources/git_source_sync_service.py +8 -1
  51. claude_mpm/services/agents/toolchain_detector.py +10 -6
  52. claude_mpm/services/analysis/__init__.py +11 -1
  53. claude_mpm/services/analysis/clone_detector.py +1030 -0
  54. claude_mpm/services/command_deployment_service.py +71 -10
  55. claude_mpm/services/event_bus/config.py +3 -1
  56. claude_mpm/services/git/git_operations_service.py +93 -8
  57. claude_mpm/services/monitor/daemon.py +9 -2
  58. claude_mpm/services/monitor/daemon_manager.py +39 -3
  59. claude_mpm/services/monitor/server.py +225 -19
  60. claude_mpm/services/self_upgrade_service.py +120 -12
  61. claude_mpm/services/skills/__init__.py +3 -0
  62. claude_mpm/services/skills/git_skill_source_manager.py +32 -2
  63. claude_mpm/services/skills/selective_skill_deployer.py +230 -0
  64. claude_mpm/services/skills/skill_to_agent_mapper.py +406 -0
  65. claude_mpm/services/skills_deployer.py +64 -3
  66. claude_mpm/services/socketio/event_normalizer.py +15 -1
  67. claude_mpm/services/socketio/server/core.py +160 -21
  68. claude_mpm/services/version_control/git_operations.py +103 -0
  69. claude_mpm/utils/agent_filters.py +17 -44
  70. {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.14.dist-info}/METADATA +47 -84
  71. {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.14.dist-info}/RECORD +76 -150
  72. claude_mpm-5.4.14.dist-info/entry_points.txt +5 -0
  73. claude_mpm-5.4.14.dist-info/licenses/LICENSE +94 -0
  74. claude_mpm-5.4.14.dist-info/licenses/LICENSE-FAQ.md +153 -0
  75. claude_mpm/agents/BASE_AGENT_TEMPLATE.md +0 -292
  76. claude_mpm/agents/BASE_DOCUMENTATION.md +0 -53
  77. claude_mpm/agents/BASE_ENGINEER.md +0 -658
  78. claude_mpm/agents/BASE_OPS.md +0 -219
  79. claude_mpm/agents/BASE_PM.md +0 -480
  80. claude_mpm/agents/BASE_PROMPT_ENGINEER.md +0 -787
  81. claude_mpm/agents/BASE_QA.md +0 -167
  82. claude_mpm/agents/BASE_RESEARCH.md +0 -53
  83. claude_mpm/agents/base_agent.json +0 -31
  84. claude_mpm/agents/base_agent_loader.py +0 -601
  85. claude_mpm/cli/ticket_cli.py +0 -35
  86. claude_mpm/commands/mpm-config-view.md +0 -150
  87. claude_mpm/dashboard/analysis_runner.py +0 -455
  88. claude_mpm/dashboard/index.html +0 -13
  89. claude_mpm/dashboard/open_dashboard.py +0 -66
  90. claude_mpm/dashboard/static/css/activity.css +0 -1958
  91. claude_mpm/dashboard/static/css/connection-status.css +0 -370
  92. claude_mpm/dashboard/static/css/dashboard.css +0 -4701
  93. claude_mpm/dashboard/static/js/components/activity-tree.js +0 -1871
  94. claude_mpm/dashboard/static/js/components/agent-hierarchy.js +0 -777
  95. claude_mpm/dashboard/static/js/components/agent-inference.js +0 -956
  96. claude_mpm/dashboard/static/js/components/build-tracker.js +0 -333
  97. claude_mpm/dashboard/static/js/components/code-simple.js +0 -857
  98. claude_mpm/dashboard/static/js/components/connection-debug.js +0 -654
  99. claude_mpm/dashboard/static/js/components/diff-viewer.js +0 -891
  100. claude_mpm/dashboard/static/js/components/event-processor.js +0 -542
  101. claude_mpm/dashboard/static/js/components/event-viewer.js +0 -1155
  102. claude_mpm/dashboard/static/js/components/export-manager.js +0 -368
  103. claude_mpm/dashboard/static/js/components/file-change-tracker.js +0 -443
  104. claude_mpm/dashboard/static/js/components/file-change-viewer.js +0 -690
  105. claude_mpm/dashboard/static/js/components/file-tool-tracker.js +0 -724
  106. claude_mpm/dashboard/static/js/components/file-viewer.js +0 -580
  107. claude_mpm/dashboard/static/js/components/hud-library-loader.js +0 -211
  108. claude_mpm/dashboard/static/js/components/hud-manager.js +0 -671
  109. claude_mpm/dashboard/static/js/components/hud-visualizer.js +0 -1718
  110. claude_mpm/dashboard/static/js/components/module-viewer.js +0 -2764
  111. claude_mpm/dashboard/static/js/components/session-manager.js +0 -579
  112. claude_mpm/dashboard/static/js/components/socket-manager.js +0 -368
  113. claude_mpm/dashboard/static/js/components/ui-state-manager.js +0 -749
  114. claude_mpm/dashboard/static/js/components/unified-data-viewer.js +0 -1824
  115. claude_mpm/dashboard/static/js/components/working-directory.js +0 -920
  116. claude_mpm/dashboard/static/js/connection-manager.js +0 -536
  117. claude_mpm/dashboard/static/js/dashboard.js +0 -1914
  118. claude_mpm/dashboard/static/js/extension-error-handler.js +0 -164
  119. claude_mpm/dashboard/static/js/socket-client.js +0 -1474
  120. claude_mpm/dashboard/static/js/tab-isolation-fix.js +0 -185
  121. claude_mpm/dashboard/static/socket.io.min.js +0 -7
  122. claude_mpm/dashboard/static/socket.io.v4.8.1.backup.js +0 -7
  123. claude_mpm/dashboard/templates/code_simple.html +0 -153
  124. claude_mpm/dashboard/templates/index.html +0 -606
  125. claude_mpm/dashboard/test_dashboard.html +0 -372
  126. claude_mpm/scripts/mcp_server.py +0 -75
  127. claude_mpm/scripts/mcp_wrapper.py +0 -39
  128. claude_mpm/services/mcp_gateway/__init__.py +0 -159
  129. claude_mpm/services/mcp_gateway/auto_configure.py +0 -369
  130. claude_mpm/services/mcp_gateway/config/__init__.py +0 -17
  131. claude_mpm/services/mcp_gateway/config/config_loader.py +0 -296
  132. claude_mpm/services/mcp_gateway/config/config_schema.py +0 -243
  133. claude_mpm/services/mcp_gateway/config/configuration.py +0 -429
  134. claude_mpm/services/mcp_gateway/core/__init__.py +0 -43
  135. claude_mpm/services/mcp_gateway/core/base.py +0 -312
  136. claude_mpm/services/mcp_gateway/core/exceptions.py +0 -253
  137. claude_mpm/services/mcp_gateway/core/interfaces.py +0 -443
  138. claude_mpm/services/mcp_gateway/core/process_pool.py +0 -977
  139. claude_mpm/services/mcp_gateway/core/singleton_manager.py +0 -315
  140. claude_mpm/services/mcp_gateway/core/startup_verification.py +0 -316
  141. claude_mpm/services/mcp_gateway/main.py +0 -589
  142. claude_mpm/services/mcp_gateway/registry/__init__.py +0 -12
  143. claude_mpm/services/mcp_gateway/registry/service_registry.py +0 -412
  144. claude_mpm/services/mcp_gateway/registry/tool_registry.py +0 -489
  145. claude_mpm/services/mcp_gateway/server/__init__.py +0 -15
  146. claude_mpm/services/mcp_gateway/server/mcp_gateway.py +0 -414
  147. claude_mpm/services/mcp_gateway/server/stdio_handler.py +0 -372
  148. claude_mpm/services/mcp_gateway/server/stdio_server.py +0 -712
  149. claude_mpm/services/mcp_gateway/tools/__init__.py +0 -36
  150. claude_mpm/services/mcp_gateway/tools/base_adapter.py +0 -485
  151. claude_mpm/services/mcp_gateway/tools/document_summarizer.py +0 -789
  152. claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +0 -654
  153. claude_mpm/services/mcp_gateway/tools/health_check_tool.py +0 -456
  154. claude_mpm/services/mcp_gateway/tools/hello_world.py +0 -551
  155. claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +0 -555
  156. claude_mpm/services/mcp_gateway/utils/__init__.py +0 -14
  157. claude_mpm/services/mcp_gateway/utils/package_version_checker.py +0 -160
  158. claude_mpm/services/mcp_gateway/utils/update_preferences.py +0 -170
  159. claude_mpm-5.1.9.dist-info/entry_points.txt +0 -10
  160. claude_mpm-5.1.9.dist-info/licenses/LICENSE +0 -21
  161. {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.14.dist-info}/WHEEL +0 -0
  162. {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.14.dist-info}/top_level.txt +0 -0
@@ -13,6 +13,7 @@ agent outputs because:
13
13
  """
14
14
 
15
15
  import re
16
+ from datetime import datetime
16
17
  from typing import Dict, List
17
18
 
18
19
  from claude_mpm.core.config import Config
@@ -47,6 +48,16 @@ except ImportError as e:
47
48
  SOCKETIO_AVAILABLE = False
48
49
  get_socketio_server = None
49
50
 
51
+ # Try to import event bus with fallback handling
52
+ try:
53
+ from claude_mpm.services.event_bus.event_bus import EventBus
54
+
55
+ EVENT_BUS_AVAILABLE = True
56
+ except ImportError as e:
57
+ logger.debug(f"EventBus not available: {e}")
58
+ EVENT_BUS_AVAILABLE = False
59
+ EventBus = None
60
+
50
61
 
51
62
  class MemoryPreDelegationHook(PreDelegationHook):
52
63
  """Inject agent memory into delegation context.
@@ -83,6 +94,16 @@ class MemoryPreDelegationHook(PreDelegationHook):
83
94
  logger.info("Memory manager not available - hook will be inactive")
84
95
  self.memory_manager = None
85
96
 
97
+ # Initialize event bus for observability
98
+ if EVENT_BUS_AVAILABLE and EventBus:
99
+ try:
100
+ self.event_bus = EventBus.get_instance()
101
+ except Exception as e:
102
+ logger.debug(f"Failed to get EventBus instance: {e}")
103
+ self.event_bus = None
104
+ else:
105
+ self.event_bus = None
106
+
86
107
  def execute(self, context: HookContext) -> HookResult:
87
108
  """Add agent memory to delegation context.
88
109
 
@@ -137,7 +158,31 @@ INSTRUCTIONS: Review your memory above before proceeding. Apply learned patterns
137
158
 
138
159
  logger.info(f"Injected memory for agent '{agent_id}'")
139
160
 
140
- # Emit Socket.IO event for memory injected
161
+ # Calculate memory size for observability
162
+ memory_size = len(memory_content)
163
+
164
+ # Emit event bus event for observability
165
+ if self.event_bus:
166
+ try:
167
+ # Determine memory source (project or user level)
168
+ # This is inferred from the memory manager's behavior
169
+ memory_source = (
170
+ "runtime" # Runtime loading from memory manager
171
+ )
172
+
173
+ self.event_bus.publish(
174
+ "agent.memory.loaded",
175
+ {
176
+ "agent_id": agent_id,
177
+ "memory_source": memory_source,
178
+ "memory_size": memory_size,
179
+ "timestamp": datetime.now(datetime.UTC).isoformat(),
180
+ },
181
+ )
182
+ except Exception as event_error:
183
+ logger.debug(f"EventBus publish failed: {event_error}")
184
+
185
+ # Emit Socket.IO event for memory injected (legacy compatibility)
141
186
  try:
142
187
  socketio_server = get_socketio_server()
143
188
  # Calculate size of injected content
claude_mpm/init.py CHANGED
@@ -141,25 +141,6 @@ class ProjectInitializer:
141
141
  if not gitignore.exists():
142
142
  gitignore.write_text("logs/\n*.log\n*.pyc\n__pycache__/\n")
143
143
 
144
- # Also ensure MCP directories are in main project .gitignore
145
- try:
146
- from claude_mpm.services.project.project_organizer import (
147
- ProjectOrganizer,
148
- )
149
-
150
- # Check if we're in a git repository
151
- if (project_root / ".git").exists():
152
- organizer = ProjectOrganizer(project_root)
153
- # This will add MCP directories and other standard patterns
154
- organizer.update_gitignore()
155
- self.logger.debug(
156
- "Updated project .gitignore with MCP and standard patterns"
157
- )
158
- except Exception as e:
159
- self.logger.debug(
160
- f"Could not update project gitignore with MCP patterns: {e}"
161
- )
162
-
163
144
  # Log successful creation with details
164
145
  self.logger.info(f"Initialized project directory at {self.project_dir}")
165
146
  self.logger.debug("Created directories: agents, config, responses, logs")
@@ -88,18 +88,21 @@ fi
88
88
  #
89
89
  # STRATEGY:
90
90
  # This function implements a fallback chain to find Python with claude-mpm dependencies:
91
- # 1. Project-specific virtual environments (venv, .venv)
92
- # 2. Currently active virtual environment ($VIRTUAL_ENV)
93
- # 3. System python3 (may lack dependencies)
94
- # 4. System python (last resort)
91
+ # 1. UV-managed projects (uv.lock detected) - uses "uv run python"
92
+ # 2. pipx installations - uses pipx venv Python
93
+ # 3. Project-specific virtual environments (venv, .venv)
94
+ # 4. Currently active virtual environment ($VIRTUAL_ENV)
95
+ # 5. System python3 (may lack dependencies)
96
+ # 6. System python (last resort)
95
97
  #
96
98
  # WHY THIS APPROACH:
97
99
  # - Claude MPM requires specific packages (socketio, eventlet) not in system Python
98
- # - Virtual environments ensure dependency isolation and availability
100
+ # - UV and virtual environments ensure dependency isolation and availability
99
101
  # - Multiple naming conventions supported (venv vs .venv)
100
102
  # - Graceful degradation to system Python if no venv found
101
103
  #
102
104
  # ACTIVATION STRATEGY:
105
+ # - UV projects: use "uv run python" to execute in UV-managed environment
103
106
  # - Sources activate script to set up environment variables
104
107
  # - Returns specific Python path for exec (not just 'python')
105
108
  # - Maintains environment in same shell process
@@ -110,10 +113,18 @@ fi
110
113
  # - Caches result in process environment
111
114
  #
112
115
  # RETURNS:
113
- # Absolute path to Python executable with claude-mpm dependencies
116
+ # Absolute path to Python executable with claude-mpm dependencies, or "uv run python" for UV projects
114
117
  #
115
118
  find_python_command() {
116
- # 1. Check if we're in a pipx installation first
119
+ # 1. Check for UV project first (uv.lock or pyproject.toml with uv)
120
+ if [ -f "$CLAUDE_MPM_ROOT/uv.lock" ]; then
121
+ if command -v uv &> /dev/null; then
122
+ echo "uv run python"
123
+ return
124
+ fi
125
+ fi
126
+
127
+ # 2. Check if we're in a pipx installation
117
128
  if [[ "$SCRIPT_DIR" == *"/.local/pipx/venvs/claude-mpm/"* ]]; then
118
129
  # pipx installation - use the pipx venv's Python directly
119
130
  if [ -f "$CLAUDE_MPM_ROOT/bin/python" ]; then
@@ -122,7 +133,7 @@ find_python_command() {
122
133
  fi
123
134
  fi
124
135
 
125
- # 2. Check for project-local virtual environment (common in development)
136
+ # 3. Check for project-local virtual environment (common in development)
126
137
  if [ -f "$CLAUDE_MPM_ROOT/venv/bin/activate" ]; then
127
138
  source "$CLAUDE_MPM_ROOT/venv/bin/activate"
128
139
  echo "$CLAUDE_MPM_ROOT/venv/bin/python"
@@ -173,15 +184,44 @@ fi
173
184
  # Set Socket.IO configuration for hook events
174
185
  export CLAUDE_MPM_SOCKETIO_PORT="${CLAUDE_MPM_SOCKETIO_PORT:-8765}"
175
186
 
176
- # Run the Python hook handler with all input
177
- # Use exec to replace the shell process with Python
178
- if ! exec "$PYTHON_CMD" -m claude_mpm.hooks.claude_hooks.hook_handler "$@" 2>/tmp/claude-mpm-hook-error.log; then
179
- # If the Python handler fails, always return continue to not block Claude
187
+ # Function for debug logging
188
+ log_debug() {
180
189
  if [ "${CLAUDE_MPM_HOOK_DEBUG}" = "true" ]; then
181
- echo "[$(date -u +%Y-%m-%dT%H:%M:%S.%3NZ)] Hook handler failed, see /tmp/claude-mpm-hook-error.log" >> /tmp/claude-mpm-hook.log
182
- echo "[$(date -u +%Y-%m-%dT%H:%M:%S.%3NZ)] Error: $(cat /tmp/claude-mpm-hook-error.log 2>/dev/null | head -5)" >> /tmp/claude-mpm-hook.log
190
+ echo "[$(date -u +%Y-%m-%dT%H:%M:%S.%3NZ)] $1" >> /tmp/claude-mpm-hook.log
191
+ fi
192
+ }
193
+
194
+ # Test Python works and module exists
195
+ # Handle UV's multi-word command specially
196
+ if [[ "$PYTHON_CMD" == "uv run python" ]]; then
197
+ if ! uv run python -c "import claude_mpm" 2>/dev/null; then
198
+ log_debug "claude_mpm module not available, continuing without hook"
199
+ echo '{"action": "continue"}'
200
+ exit 0
201
+ fi
202
+ else
203
+ if ! $PYTHON_CMD -c "import claude_mpm" 2>/dev/null; then
204
+ log_debug "claude_mpm module not available, continuing without hook"
205
+ echo '{"action": "continue"}'
206
+ exit 0
183
207
  fi
184
- # Return continue action to prevent blocking Claude Code
185
- echo '{"action": "continue"}'
186
- exit 0
187
- fi
208
+ fi
209
+
210
+ # Run the Python hook handler with all input
211
+ # Use exec to replace the shell process with Python
212
+ # Handle UV's multi-word command specially
213
+ if [[ "$PYTHON_CMD" == "uv run python" ]]; then
214
+ exec uv run python -m claude_mpm.hooks.claude_hooks.hook_handler "$@" 2>/tmp/claude-mpm-hook-error.log
215
+ else
216
+ exec "$PYTHON_CMD" -m claude_mpm.hooks.claude_hooks.hook_handler "$@" 2>/tmp/claude-mpm-hook-error.log
217
+ fi
218
+
219
+ # Note: exec replaces the shell process, so code below only runs if exec fails
220
+ # If we reach here, the Python handler failed
221
+ if [ "${CLAUDE_MPM_HOOK_DEBUG}" = "true" ]; then
222
+ echo "[$(date -u +%Y-%m-%dT%H:%M:%S.%3NZ)] Hook handler failed, see /tmp/claude-mpm-hook-error.log" >> /tmp/claude-mpm-hook.log
223
+ echo "[$(date -u +%Y-%m-%dT%H:%M:%S.%3NZ)] Error: $(cat /tmp/claude-mpm-hook-error.log 2>/dev/null | head -5)" >> /tmp/claude-mpm-hook.log
224
+ fi
225
+ # Return continue action to prevent blocking Claude Code
226
+ echo '{"action": "continue"}'
227
+ exit 0
@@ -7,6 +7,12 @@ dashboard, which includes both the Socket.IO server and web interface.
7
7
 
8
8
  WHY: Provides a simple command to start the monitoring dashboard that tracks
9
9
  Claude MPM events and agent activity in real-time.
10
+
11
+ SINGLE INSTANCE ENFORCEMENT:
12
+ - Only ONE monitor instance runs at a time on port 8765 (default)
13
+ - If monitor already running on default port: reuse existing, open browser
14
+ - If user specifies --port explicitly: use that port, fail if busy
15
+ - No auto-increment port selection (prevents multiple instances)
10
16
  """
11
17
 
12
18
  import argparse
@@ -15,12 +21,36 @@ import webbrowser
15
21
 
16
22
  from claude_mpm.core.logging_config import get_logger
17
23
  from claude_mpm.services.monitor.daemon import UnifiedMonitorDaemon
18
- from claude_mpm.services.port_manager import PortManager
24
+ from claude_mpm.services.monitor.daemon_manager import DaemonManager
19
25
 
20
26
  DEFAULT_PORT = 8765
21
27
  logger = get_logger(__name__)
22
28
 
23
29
 
30
+ def check_existing_monitor(host: str, port: int) -> bool:
31
+ """Check if monitor is already running on the specified port.
32
+
33
+ Args:
34
+ host: Host to check
35
+ port: Port to check
36
+
37
+ Returns:
38
+ True if monitor is running, False otherwise
39
+ """
40
+ try:
41
+ import requests
42
+
43
+ response = requests.get(f"http://{host}:{port}/health", timeout=2)
44
+ if response.status_code == 200:
45
+ data = response.json()
46
+ # Check if it's our claude-mpm-monitor service
47
+ if data.get("service") == "claude-mpm-monitor":
48
+ return True
49
+ except Exception:
50
+ pass
51
+ return False
52
+
53
+
24
54
  def main():
25
55
  """Main entry point for monitor launcher."""
26
56
  parser = argparse.ArgumentParser(
@@ -30,8 +60,8 @@ def main():
30
60
  parser.add_argument(
31
61
  "--port",
32
62
  type=int,
33
- default=DEFAULT_PORT,
34
- help=f"Port to run on (default: {DEFAULT_PORT})",
63
+ default=None, # Changed: None means use DEFAULT_PORT with single-instance check
64
+ help=f"Port to run on (default: {DEFAULT_PORT}). If specified, fails if port is busy.",
35
65
  )
36
66
 
37
67
  parser.add_argument(
@@ -46,20 +76,70 @@ def main():
46
76
  "--background", action="store_true", help="Run in background daemon mode"
47
77
  )
48
78
 
79
+ parser.add_argument(
80
+ "--dev",
81
+ action="store_true",
82
+ help="Enable development mode with hot reload for Svelte changes",
83
+ )
84
+
49
85
  args = parser.parse_args()
50
86
 
51
- # Find available port
52
- port_manager = PortManager()
53
- actual_port = port_manager.find_available_port(preferred_port=args.port)
87
+ # Determine target port
88
+ user_specified_port = args.port is not None
89
+ target_port = args.port if user_specified_port else DEFAULT_PORT
90
+
91
+ # SINGLE INSTANCE ENFORCEMENT:
92
+ # Check if monitor already running on target port
93
+ if check_existing_monitor(args.host, target_port):
94
+ logger.info(f"Monitor already running at http://{args.host}:{target_port}")
54
95
 
55
- if actual_port != args.port:
56
- logger.info(f"Port {args.port} is in use, using port {actual_port} instead")
96
+ # Open browser to existing instance if requested
97
+ if not args.no_browser:
98
+ url = f"http://{args.host}:{target_port}"
99
+ logger.info(f"Opening browser to existing instance: {url}")
100
+ webbrowser.open(url)
101
+
102
+ # Success - reusing existing instance
103
+ return
104
+
105
+ # Port selection logic:
106
+ # - If user specified --port: Use that exact port, fail if busy
107
+ # - If no --port: Use DEFAULT_PORT (8765), fail if busy
108
+ # - Never auto-increment to find free port
109
+
110
+ # Create daemon manager for port checking
111
+ daemon_manager = DaemonManager(port=target_port, host=args.host)
112
+
113
+ if not daemon_manager._is_port_available():
114
+ if user_specified_port:
115
+ # User explicitly requested a port - fail with clear message
116
+ logger.error(
117
+ f"Port {target_port} is already in use by another service. "
118
+ f"Please stop the existing service or choose a different port."
119
+ )
120
+ sys.exit(1)
121
+ else:
122
+ # Default port is busy - fail with helpful message
123
+ logger.error(
124
+ f"Default port {DEFAULT_PORT} is already in use by another service. "
125
+ f"Please stop the existing service with 'claude-mpm monitor stop' "
126
+ f"or specify a different port with --port."
127
+ )
128
+ sys.exit(1)
57
129
 
58
130
  # Start the monitor daemon
59
- logger.info(f"Starting Claude MPM monitor on {args.host}:{actual_port}")
131
+ if args.dev:
132
+ logger.info(
133
+ f"Starting Claude MPM monitor on {args.host}:{target_port} (DEV MODE - hot reload enabled)"
134
+ )
135
+ else:
136
+ logger.info(f"Starting Claude MPM monitor on {args.host}:{target_port}")
60
137
 
61
138
  daemon = UnifiedMonitorDaemon(
62
- host=args.host, port=actual_port, daemon_mode=args.background
139
+ host=args.host,
140
+ port=target_port,
141
+ daemon_mode=args.background,
142
+ enable_hot_reload=args.dev,
63
143
  )
64
144
 
65
145
  success = daemon.start()
@@ -67,14 +147,14 @@ def main():
67
147
  if success:
68
148
  # Open browser if requested
69
149
  if not args.no_browser:
70
- url = f"http://{args.host}:{actual_port}"
150
+ url = f"http://{args.host}:{target_port}"
71
151
  logger.info(f"Opening browser to {url}")
72
152
  webbrowser.open(url)
73
153
 
74
154
  if args.background:
75
- logger.info(f"Monitor daemon started in background on port {actual_port}")
155
+ logger.info(f"Monitor daemon started in background on port {target_port}")
76
156
  else:
77
- logger.info(f"Monitor running on port {actual_port}")
157
+ logger.info(f"Monitor running on port {target_port}")
78
158
  logger.info("Press Ctrl+C to stop")
79
159
  else:
80
160
  logger.error("Failed to start monitor")
@@ -0,0 +1,278 @@
1
+ """Agent Recommendation Service
2
+
3
+ WHY: Provides intelligent agent recommendations based on toolchain detection
4
+ and always-recommended core agents. Helps users discover and install the
5
+ most relevant agents for their project without manual selection.
6
+
7
+ DESIGN DECISION: Uses toolchain analysis to map detected languages/frameworks
8
+ to specific engineer agents, plus always includes core agents.
9
+
10
+ Architecture:
11
+ - Toolchain-based recommendations: Python → python-engineer, etc.
12
+ - Core agents (always recommended): engineer, qa-agent, memory-manager-agent, local-ops-agent,
13
+ research-agent, documentation-agent, security-agent
14
+ - Confidence-based filtering: Only recommend high-confidence detections
15
+ """
16
+
17
+ from pathlib import Path
18
+ from typing import Dict, List, Optional, Set
19
+
20
+ from ...services.project.toolchain_analyzer import ToolchainAnalyzerService
21
+
22
+
23
+ class AgentRecommendationService:
24
+ """Service for recommending agents based on project toolchain.
25
+
26
+ WHY: Users shouldn't have to manually figure out which agents to install.
27
+ This service provides intelligent recommendations based on detected stack.
28
+
29
+ DESIGN DECISION: Separated from configure.py for reusability and testability.
30
+ Can be used by CLI, API, or future auto-configuration features.
31
+ """
32
+
33
+ # Core agents always included - matches ToolchainDetector.CORE_AGENTS
34
+ # Uses exact agent IDs from repository for consistency
35
+ CORE_AGENTS = {
36
+ "engineer",
37
+ "qa-agent",
38
+ "memory-manager-agent",
39
+ "local-ops-agent",
40
+ "research-agent",
41
+ "documentation-agent",
42
+ "security-agent",
43
+ }
44
+
45
+ # Map detected languages to recommended engineer agents
46
+ LANGUAGE_TO_AGENTS: Dict[str, List[str]] = {
47
+ "python": [
48
+ "engineer/backend/python-engineer",
49
+ "qa/api-qa",
50
+ ],
51
+ "javascript": [
52
+ "engineer/backend/javascript-engineer",
53
+ "engineer/data/typescript-engineer",
54
+ ],
55
+ "typescript": [
56
+ "engineer/data/typescript-engineer",
57
+ "engineer/backend/javascript-engineer",
58
+ ],
59
+ "rust": [
60
+ "engineer/backend/rust-engineer",
61
+ ],
62
+ "go": [
63
+ "engineer/backend/golang-engineer",
64
+ ],
65
+ "java": [
66
+ "engineer/backend/java-engineer",
67
+ ],
68
+ "dart": [
69
+ "engineer/mobile/dart-engineer",
70
+ ],
71
+ "php": [
72
+ "engineer/backend/php-engineer",
73
+ ],
74
+ "ruby": [
75
+ "engineer/backend/ruby-engineer",
76
+ ],
77
+ "swift": [
78
+ "engineer/mobile/swift-engineer",
79
+ ],
80
+ "kotlin": [
81
+ "engineer/mobile/kotlin-engineer",
82
+ ],
83
+ }
84
+
85
+ # Map detected frameworks to recommended agents
86
+ FRAMEWORK_TO_AGENTS: Dict[str, List[str]] = {
87
+ # Frontend frameworks
88
+ "react": [
89
+ "engineer/frontend/react-engineer",
90
+ "qa/web-qa",
91
+ ],
92
+ "nextjs": [
93
+ "engineer/frontend/nextjs-engineer",
94
+ "engineer/frontend/react-engineer",
95
+ "ops/platform/vercel-ops",
96
+ ],
97
+ "vue": [
98
+ "engineer/frontend/vue-engineer",
99
+ ],
100
+ "angular": [
101
+ "engineer/frontend/angular-engineer",
102
+ ],
103
+ # Backend frameworks
104
+ "fastapi": [
105
+ "engineer/backend/python-engineer",
106
+ "qa/api-qa",
107
+ ],
108
+ "django": [
109
+ "engineer/backend/python-engineer",
110
+ "qa/api-qa",
111
+ ],
112
+ "flask": [
113
+ "engineer/backend/python-engineer",
114
+ "qa/api-qa",
115
+ ],
116
+ "express": [
117
+ "engineer/backend/javascript-engineer",
118
+ "qa/api-qa",
119
+ ],
120
+ "nest": [
121
+ "engineer/backend/javascript-engineer",
122
+ "qa/api-qa",
123
+ ],
124
+ # Mobile frameworks
125
+ "flutter": [
126
+ "engineer/mobile/dart-engineer",
127
+ ],
128
+ "react-native": [
129
+ "engineer/frontend/react-engineer",
130
+ "engineer/mobile/react-native-engineer",
131
+ ],
132
+ # Desktop frameworks
133
+ "tauri": [
134
+ "engineer/mobile/tauri-engineer",
135
+ "engineer/backend/rust-engineer",
136
+ ],
137
+ "electron": [
138
+ "engineer/backend/javascript-engineer",
139
+ ],
140
+ }
141
+
142
+ def __init__(self, toolchain_analyzer: Optional[ToolchainAnalyzerService] = None):
143
+ """Initialize agent recommendation service.
144
+
145
+ Args:
146
+ toolchain_analyzer: Optional pre-initialized toolchain analyzer.
147
+ If None, creates a new instance.
148
+ """
149
+ self.toolchain_analyzer = toolchain_analyzer or ToolchainAnalyzerService()
150
+
151
+ def get_recommended_agents(
152
+ self,
153
+ project_path: Optional[str] = None,
154
+ confidence_threshold: float = 0.5,
155
+ ) -> Set[str]:
156
+ """Get recommended agents for a project.
157
+
158
+ Args:
159
+ project_path: Path to project directory. Defaults to cwd.
160
+ confidence_threshold: Minimum confidence for recommendations (0.0-1.0).
161
+ Only include detected components above this threshold.
162
+
163
+ Returns:
164
+ Set of recommended agent IDs (e.g., {"qa-agent", "research-agent", ...})
165
+
166
+ Example:
167
+ >>> service = AgentRecommendationService()
168
+ >>> recommended = service.get_recommended_agents()
169
+ >>> "qa-agent" in recommended
170
+ True
171
+ >>> # For Python project:
172
+ >>> "engineer/backend/python-engineer" in recommended
173
+ True
174
+ """
175
+ # Start with core agents (always recommended)
176
+ recommended = self.CORE_AGENTS.copy()
177
+
178
+ # Analyze project toolchain
179
+ if project_path is None:
180
+ project_path = str(Path.cwd())
181
+
182
+ try:
183
+ # ToolchainAnalyzerService.analyze_toolchain takes Path, not str
184
+ analysis = self.toolchain_analyzer.analyze_toolchain(Path(project_path))
185
+ except Exception as e:
186
+ # If analysis fails, just return core agents
187
+ print(f"Warning: Toolchain analysis failed: {e}")
188
+ return recommended
189
+
190
+ # Add language-specific agents
191
+ # ToolchainAnalysis has a single LanguageDetection object, not a list
192
+ if analysis.language_detection:
193
+ # Check primary language
194
+ primary_lang = analysis.language_detection.primary_language.lower()
195
+ if primary_lang in self.LANGUAGE_TO_AGENTS:
196
+ recommended.update(self.LANGUAGE_TO_AGENTS[primary_lang])
197
+
198
+ # Check secondary languages
199
+ for lang_component in analysis.language_detection.secondary_languages:
200
+ lang = lang_component.name.lower()
201
+ if lang in self.LANGUAGE_TO_AGENTS:
202
+ recommended.update(self.LANGUAGE_TO_AGENTS[lang])
203
+
204
+ # Add framework-specific agents
205
+ for framework in analysis.frameworks:
206
+ fw_name = framework.name.lower()
207
+ if fw_name in self.FRAMEWORK_TO_AGENTS:
208
+ recommended.update(self.FRAMEWORK_TO_AGENTS[fw_name])
209
+
210
+ return recommended
211
+
212
+ def get_detection_summary(self, project_path: Optional[str] = None) -> Dict:
213
+ """Get human-readable summary of detected toolchain.
214
+
215
+ Args:
216
+ project_path: Path to project directory. Defaults to cwd.
217
+
218
+ Returns:
219
+ Dict with keys:
220
+ - detected_languages: List of detected language names
221
+ - detected_frameworks: List of detected framework names
222
+ - recommended_count: Number of recommended agents
223
+ - detection_quality: "high", "medium", "low", or "none"
224
+
225
+ Example:
226
+ >>> summary = service.get_detection_summary()
227
+ >>> summary['detected_languages']
228
+ ['Python', 'JavaScript']
229
+ >>> summary['recommended_count']
230
+ 15
231
+ """
232
+ if project_path is None:
233
+ project_path = str(Path.cwd())
234
+
235
+ try:
236
+ analysis = self.toolchain_analyzer.analyze_toolchain(Path(project_path))
237
+
238
+ # Extract languages from LanguageDetection object
239
+ languages = []
240
+ if analysis.language_detection:
241
+ languages.append(analysis.language_detection.primary_language)
242
+ languages.extend(
243
+ comp.name
244
+ for comp in analysis.language_detection.secondary_languages
245
+ )
246
+
247
+ # Extract frameworks
248
+ frameworks = [fw.name for fw in analysis.frameworks]
249
+
250
+ # Get recommended agents
251
+ recommended = self.get_recommended_agents(project_path)
252
+
253
+ # Determine detection quality from overall_confidence
254
+ confidence_map = {
255
+ "high": "high",
256
+ "medium": "medium",
257
+ "low": "low",
258
+ "very_low": "low",
259
+ }
260
+ quality = confidence_map.get(
261
+ str(analysis.overall_confidence).lower(), "unknown"
262
+ )
263
+
264
+ return {
265
+ "detected_languages": languages,
266
+ "detected_frameworks": frameworks,
267
+ "recommended_count": len(recommended),
268
+ "detection_quality": quality,
269
+ }
270
+ except Exception as e:
271
+ # Log the error for debugging
272
+ print(f"Warning: Toolchain analysis failed: {e}")
273
+ return {
274
+ "detected_languages": [],
275
+ "detected_frameworks": [],
276
+ "recommended_count": len(self.CORE_AGENTS),
277
+ "detection_quality": "none",
278
+ }