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
@@ -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")
@@ -103,6 +103,10 @@ class AgentMetadata:
103
103
  author: Optional[str] = None
104
104
  tags: List[str] = field(default_factory=list)
105
105
  specializations: List[str] = field(default_factory=list)
106
+ # NEW: Collection metadata for enhanced agent matching
107
+ collection_id: Optional[str] = None # Format: owner/repo-name
108
+ source_path: Optional[str] = None # Relative path in repository
109
+ canonical_id: Optional[str] = None # Format: collection_id:agent_id
106
110
 
107
111
  def increment_serial_version(self) -> None:
108
112
  """Increment the patch version number.
@@ -181,6 +185,9 @@ class AgentDefinition:
181
185
  "author": self.metadata.author,
182
186
  "tags": self.metadata.tags,
183
187
  "specializations": self.metadata.specializations,
188
+ "collection_id": self.metadata.collection_id,
189
+ "source_path": self.metadata.source_path,
190
+ "canonical_id": self.metadata.canonical_id,
184
191
  },
185
192
  "primary_role": self.primary_role,
186
193
  "when_to_use": self.when_to_use,
@@ -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")
File without changes
@@ -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
+ }