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
@@ -1,455 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Code Analysis Runner for Dashboard
4
- ===================================
5
-
6
- WHY: Manages subprocess execution of code analysis, streaming results to
7
- Socket.IO clients in real-time while handling cancellation and error recovery.
8
-
9
- DESIGN DECISIONS:
10
- - Use subprocess for isolation and cancellation support
11
- - Stream output line-by-line for real-time updates
12
- - Queue multiple analysis requests
13
- - Handle process lifecycle management
14
- - Convert analyzer events to Socket.IO events
15
- """
16
-
17
- import json
18
- import os
19
- import subprocess
20
- import sys
21
- import threading
22
- from dataclasses import asdict, dataclass
23
- from datetime import datetime, timezone
24
- from pathlib import Path
25
- from queue import Queue
26
- from typing import Any, Dict, List, Optional
27
-
28
- from ..core.logging_config import get_logger
29
-
30
-
31
- @dataclass
32
- class AnalysisRequest:
33
- """Represents a code analysis request."""
34
-
35
- request_id: str
36
- path: str
37
- languages: Optional[List[str]] = None
38
- max_depth: Optional[int] = None
39
- ignore_patterns: Optional[List[str]] = None
40
- timestamp: datetime = None
41
-
42
- def __post_init__(self):
43
- if self.timestamp is None:
44
- self.timestamp = datetime.now(timezone.utc)
45
-
46
-
47
- class CodeAnalysisRunner:
48
- """Manages code analysis subprocess execution for the dashboard.
49
-
50
- WHY: Provides isolation between the dashboard server and analysis process,
51
- allowing for cancellation, resource limits, and crash recovery.
52
- """
53
-
54
- def __init__(self, socketio_server):
55
- """Initialize the analysis runner.
56
-
57
- Args:
58
- socketio_server: SocketIOServer instance for broadcasting events
59
- """
60
- self.logger = get_logger(__name__)
61
- self.server = socketio_server
62
- self.current_process = None
63
- self.current_request = None
64
- self.request_queue = Queue()
65
- self.running = False
66
- self.worker_thread = None
67
- self.cancel_event = threading.Event()
68
-
69
- # Statistics
70
- self.stats = {
71
- "analyses_started": 0,
72
- "analyses_completed": 0,
73
- "analyses_cancelled": 0,
74
- "analyses_failed": 0,
75
- "total_files": 0,
76
- "total_nodes": 0,
77
- }
78
-
79
- def start(self):
80
- """Start the analysis runner worker thread."""
81
- if self.running:
82
- return
83
-
84
- self.running = True
85
- self.cancel_event.clear()
86
- self.worker_thread = threading.Thread(target=self._worker_loop, daemon=True)
87
- self.worker_thread.start()
88
- self.logger.info("Code analysis runner started")
89
-
90
- def stop(self):
91
- """Stop the analysis runner and cleanup."""
92
- self.running = False
93
- self.cancel_current()
94
-
95
- # Add sentinel to queue to wake up worker
96
- self.request_queue.put(None)
97
-
98
- if self.worker_thread:
99
- self.worker_thread.join(timeout=5)
100
-
101
- self.logger.info("Code analysis runner stopped")
102
-
103
- def request_analysis(
104
- self,
105
- request_id: str,
106
- path: str,
107
- languages: Optional[List[str]] = None,
108
- max_depth: Optional[int] = None,
109
- ignore_patterns: Optional[List[str]] = None,
110
- ) -> bool:
111
- """Queue a new analysis request.
112
-
113
- Args:
114
- request_id: Unique request identifier
115
- path: Directory path to analyze
116
- languages: Optional list of languages to filter
117
- max_depth: Optional maximum directory depth
118
- ignore_patterns: Optional list of patterns to ignore
119
-
120
- Returns:
121
- True if request was queued successfully
122
- """
123
- # Validate path
124
- analysis_path = Path(path).resolve()
125
- if not analysis_path.exists():
126
- self._emit_error(request_id, f"Path does not exist: {path}")
127
- return False
128
-
129
- if not analysis_path.is_dir():
130
- self._emit_error(request_id, f"Path is not a directory: {path}")
131
- return False
132
-
133
- # Create request
134
- request = AnalysisRequest(
135
- request_id=request_id,
136
- path=str(analysis_path),
137
- languages=languages,
138
- max_depth=max_depth,
139
- ignore_patterns=ignore_patterns,
140
- )
141
-
142
- # Queue request
143
- self.request_queue.put(request)
144
- self.logger.info(f"Queued analysis request {request_id} for {path}")
145
-
146
- # Emit queued event
147
- self._emit_event(
148
- "code:analysis:queued",
149
- {
150
- "request_id": request_id,
151
- "path": str(analysis_path),
152
- "queue_size": self.request_queue.qsize(),
153
- },
154
- )
155
-
156
- return True
157
-
158
- def cancel_current(self):
159
- """Cancel the currently running analysis."""
160
- if self.current_process and self.current_process.poll() is None:
161
- self.cancel_event.set()
162
-
163
- # Try graceful termination first
164
- self.current_process.terminate()
165
- try:
166
- self.current_process.wait(timeout=2)
167
- except subprocess.TimeoutExpired:
168
- # Force kill if needed
169
- self.current_process.kill()
170
- self.current_process.wait()
171
-
172
- self.stats["analyses_cancelled"] += 1
173
-
174
- if self.current_request:
175
- self._emit_event(
176
- "code:analysis:cancelled",
177
- {
178
- "request_id": self.current_request.request_id,
179
- "path": self.current_request.path,
180
- },
181
- )
182
-
183
- self.logger.info("Cancelled current analysis")
184
-
185
- def get_status(self) -> Dict[str, Any]:
186
- """Get current runner status.
187
-
188
- Returns:
189
- Dictionary with current status and statistics
190
- """
191
- return {
192
- "running": self.running,
193
- "current_request": (
194
- asdict(self.current_request) if self.current_request else None
195
- ),
196
- "queue_size": self.request_queue.qsize(),
197
- "stats": self.stats.copy(),
198
- }
199
-
200
- def _worker_loop(self):
201
- """Worker thread loop for processing analysis requests."""
202
- while self.running:
203
- try:
204
- # Get next request (blocking with timeout)
205
- request = self.request_queue.get(timeout=1)
206
-
207
- if request is None: # Sentinel value
208
- break
209
-
210
- # Reset cancel event
211
- self.cancel_event.clear()
212
-
213
- # Process request
214
- self._process_request(request)
215
-
216
- except Exception as e:
217
- self.logger.error(f"Error in worker loop: {e}")
218
-
219
- def _process_request(self, request: AnalysisRequest):
220
- """Process a single analysis request.
221
-
222
- Args:
223
- request: The analysis request to process
224
- """
225
- self.current_request = request
226
- self.stats["analyses_started"] += 1
227
-
228
- try:
229
- # Emit start event
230
- self._emit_event(
231
- "code:analysis:start",
232
- {
233
- "request_id": request.request_id,
234
- "path": request.path,
235
- "languages": request.languages,
236
- "timestamp": request.timestamp.isoformat(),
237
- },
238
- )
239
-
240
- # Build command
241
- cmd = self._build_command(request)
242
- self.logger.info(f"Starting analysis subprocess: {' '.join(cmd)}")
243
-
244
- # Start subprocess
245
- try:
246
- self.current_process = subprocess.Popen(
247
- cmd,
248
- stdout=subprocess.PIPE,
249
- stderr=subprocess.PIPE,
250
- text=True,
251
- bufsize=1,
252
- universal_newlines=True,
253
- env=self._get_subprocess_env(),
254
- )
255
- self.logger.debug(
256
- f"Subprocess started with PID: {self.current_process.pid}"
257
- )
258
- except FileNotFoundError as e:
259
- raise subprocess.SubprocessError(
260
- f"Python executable not found: {cmd[0]}"
261
- ) from e
262
- except Exception as e:
263
- raise subprocess.SubprocessError(
264
- f"Failed to start subprocess: {e}"
265
- ) from e
266
-
267
- # Process output
268
- self._process_output(request)
269
-
270
- # Wait for completion
271
- return_code = self.current_process.wait()
272
-
273
- if self.cancel_event.is_set():
274
- # Analysis was cancelled
275
- pass # Event already emitted in cancel_current
276
- elif return_code == 0:
277
- # Success
278
- self.stats["analyses_completed"] += 1
279
- self._emit_event(
280
- "code:analysis:complete",
281
- {
282
- "request_id": request.request_id,
283
- "path": request.path,
284
- "stats": {
285
- "total_files": self.stats["total_files"],
286
- "total_nodes": self.stats["total_nodes"],
287
- },
288
- },
289
- )
290
- else:
291
- # Failure - capture any remaining stderr
292
- stderr_output = ""
293
- if self.current_process.stderr:
294
- try:
295
- # Read any remaining stderr output
296
- stderr_lines = []
297
- for line in self.current_process.stderr:
298
- stderr_lines.append(line.strip())
299
- stderr_output = "\n".join(stderr_lines)
300
- except Exception as e:
301
- stderr_output = f"Failed to read stderr: {e}"
302
-
303
- self.stats["analyses_failed"] += 1
304
- error_msg = f"Analysis failed with code {return_code}"
305
- if stderr_output:
306
- error_msg += f": {stderr_output}"
307
-
308
- self.logger.error(f"Subprocess failed: {error_msg}")
309
- self._emit_error(request.request_id, error_msg)
310
-
311
- except subprocess.SubprocessError as e:
312
- self.logger.error(f"Subprocess error for request {request.request_id}: {e}")
313
- self.stats["analyses_failed"] += 1
314
- self._emit_error(request.request_id, f"Failed to start analyzer: {e}")
315
- except Exception as e:
316
- self.logger.error(
317
- f"Error processing request {request.request_id}: {e}", exc_info=True
318
- )
319
- self.stats["analyses_failed"] += 1
320
- self._emit_error(request.request_id, str(e))
321
-
322
- finally:
323
- self.current_process = None
324
- self.current_request = None
325
-
326
- def _build_command(self, request: AnalysisRequest) -> List[str]:
327
- """Build the subprocess command for analysis.
328
-
329
- Args:
330
- request: The analysis request
331
-
332
- Returns:
333
- Command list for subprocess.Popen
334
- """
335
- # Get Python executable
336
- python_exe = sys.executable
337
-
338
- # Build command - use the CLI analyze-code command
339
- cmd = [
340
- python_exe,
341
- "-m",
342
- "claude_mpm",
343
- "analyze-code",
344
- request.path,
345
- "--emit-events",
346
- "--output",
347
- "json",
348
- ]
349
-
350
- # Add optional parameters
351
- if request.languages:
352
- cmd.extend(["--languages", ",".join(request.languages)])
353
-
354
- if request.max_depth:
355
- cmd.extend(["--max-depth", str(request.max_depth)])
356
-
357
- if request.ignore_patterns:
358
- for pattern in request.ignore_patterns:
359
- cmd.extend(["--ignore", pattern])
360
-
361
- return cmd
362
-
363
- def _get_subprocess_env(self) -> Dict[str, str]:
364
- """Get environment variables for subprocess.
365
-
366
- Returns:
367
- Environment dictionary for subprocess
368
- """
369
- env = os.environ.copy()
370
-
371
- # Ensure Socket.IO URL is set for event emission
372
- env["SOCKETIO_URL"] = f"http://localhost:{self.server.port}"
373
-
374
- # Set Python path to include our modules
375
- python_path = env.get("PYTHONPATH", "")
376
- src_path = str(Path(__file__).parent.parent.parent)
377
- if src_path not in python_path:
378
- env["PYTHONPATH"] = f"{src_path}:{python_path}" if python_path else src_path
379
-
380
- return env
381
-
382
- def _process_output(self, request: AnalysisRequest):
383
- """Process subprocess output and emit events.
384
-
385
- Args:
386
- request: The current analysis request
387
- """
388
- if not self.current_process:
389
- return
390
-
391
- # Read output line by line
392
- for line in iter(self.current_process.stdout.readline, ""):
393
- if self.cancel_event.is_set():
394
- break
395
-
396
- line = line.strip()
397
- if not line:
398
- continue
399
-
400
- try:
401
- # Parse JSON event
402
- event = json.loads(line)
403
-
404
- # Route event to appropriate handler
405
- event_type = event.get("type")
406
- event_data = event.get("data", {})
407
-
408
- # Add request ID to event data
409
- event_data["request_id"] = request.request_id
410
-
411
- # Update statistics based on event type
412
- if event_type == "code:file:complete":
413
- self.stats["total_files"] += 1
414
- elif event_type == "code:node:found":
415
- self.stats["total_nodes"] += 1
416
-
417
- # Emit to Socket.IO clients
418
- self._emit_event(event_type, event_data)
419
-
420
- except json.JSONDecodeError:
421
- # Not JSON, treat as log message
422
- self.logger.debug(f"Analyzer output: {line}")
423
- except Exception as e:
424
- self.logger.warning(f"Error processing analyzer output: {e}")
425
-
426
- def _emit_event(self, event_type: str, data: Dict[str, Any]):
427
- """Emit an event to Socket.IO clients.
428
-
429
- Args:
430
- event_type: Type of event
431
- data: Event data
432
- """
433
- if self.server:
434
- # Add timestamp if not present
435
- if "timestamp" not in data:
436
- data["timestamp"] = datetime.now(timezone.utc).isoformat()
437
-
438
- # Broadcast to all clients
439
- self.server.broadcast_event(event_type, data)
440
-
441
- def _emit_error(self, request_id: str, message: str):
442
- """Emit an error event.
443
-
444
- Args:
445
- request_id: Request that caused the error
446
- message: Error message
447
- """
448
- self._emit_event(
449
- "code:analysis:error",
450
- {
451
- "request_id": request_id,
452
- "message": message,
453
- "timestamp": datetime.now(timezone.utc).isoformat(),
454
- },
455
- )
@@ -1,13 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Claude MPM Dashboard</title>
7
- <meta http-equiv="refresh" content="0; url=/dashboard?autoconnect=true&port=8765">
8
- </head>
9
- <body>
10
- <p>Redirecting to Claude MPM Dashboard...</p>
11
- <p>If you are not redirected, <a href="/dashboard?autoconnect=true&port=8765">click here</a>.</p>
12
- </body>
13
- </html>
@@ -1,66 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Open the dashboard statically in the browser."""
3
-
4
- import webbrowser
5
- from pathlib import Path
6
-
7
- try:
8
- from ..services.port_manager import PortManager
9
- except ImportError:
10
- # Fallback for when running as standalone script
11
- import sys
12
-
13
- sys.path.insert(0, str(Path(__file__).parent.parent.parent))
14
- from claude_mpm.services.port_manager import PortManager
15
-
16
-
17
- def discover_socketio_port():
18
- """Discover the port of the running SocketIO server, preferring 8765."""
19
- try:
20
- port_manager = PortManager()
21
- instances = port_manager.list_active_instances()
22
-
23
- if instances:
24
- # First, check if port 8765 is being used
25
- for instance in instances:
26
- if instance.get("port") == 8765:
27
- return 8765
28
-
29
- # If 8765 is not available, return the first active instance port
30
- return instances[0].get("port", 8765)
31
- print("⚠️ No active SocketIO instances found, using default port 8765")
32
- return 8765
33
- except Exception as e:
34
- print(f"⚠️ Failed to discover SocketIO port: {e}")
35
- print(" Using default port 8765")
36
- return 8765
37
-
38
-
39
- def open_dashboard(port=8765, autoconnect=True):
40
- """Open the dashboard HTML file directly in the browser.
41
-
42
- Args:
43
- port: Socket.IO server port to connect to (defaults to 8765, auto-discovers if needed)
44
- autoconnect: Whether to auto-connect on load
45
- """
46
- # If default port 8765 is specified, check if we need to auto-discover
47
- if port == 8765:
48
- discovered_port = discover_socketio_port()
49
- if discovered_port != 8765:
50
- print(
51
- f"🔍 SocketIO server found on port {discovered_port} instead of default 8765"
52
- )
53
- port = discovered_port
54
- # Build HTTP URL to connect to the SocketIO server's dashboard
55
- dashboard_url = f"http://localhost:{port}"
56
-
57
- print(f"🌐 Opening dashboard: {dashboard_url}")
58
- print(f"📡 Dashboard served by Socket.IO server at localhost:{port}")
59
- webbrowser.open(dashboard_url)
60
-
61
- return dashboard_url
62
-
63
-
64
- if __name__ == "__main__":
65
- # Test opening the dashboard
66
- open_dashboard()