claude-mpm 5.0.2__py3-none-any.whl → 5.4.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of claude-mpm might be problematic. Click here for more details.

Files changed (184) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md +2002 -0
  3. claude_mpm/agents/PM_INSTRUCTIONS.md +1218 -905
  4. claude_mpm/agents/agent_loader.py +10 -17
  5. claude_mpm/agents/base_agent_loader.py +10 -35
  6. claude_mpm/agents/frontmatter_validator.py +68 -0
  7. claude_mpm/agents/templates/circuit-breakers.md +431 -45
  8. claude_mpm/cli/__init__.py +0 -1
  9. claude_mpm/cli/commands/__init__.py +2 -0
  10. claude_mpm/cli/commands/agent_state_manager.py +67 -23
  11. claude_mpm/cli/commands/agents.py +446 -25
  12. claude_mpm/cli/commands/auto_configure.py +535 -233
  13. claude_mpm/cli/commands/configure.py +1500 -147
  14. claude_mpm/cli/commands/configure_agent_display.py +13 -6
  15. claude_mpm/cli/commands/mpm_init/core.py +158 -1
  16. claude_mpm/cli/commands/mpm_init/knowledge_extractor.py +481 -0
  17. claude_mpm/cli/commands/mpm_init/prompts.py +280 -0
  18. claude_mpm/cli/commands/postmortem.py +401 -0
  19. claude_mpm/cli/commands/run.py +1 -39
  20. claude_mpm/cli/commands/skills.py +322 -19
  21. claude_mpm/cli/commands/summarize.py +413 -0
  22. claude_mpm/cli/executor.py +8 -0
  23. claude_mpm/cli/interactive/agent_wizard.py +302 -195
  24. claude_mpm/cli/parsers/agents_parser.py +137 -0
  25. claude_mpm/cli/parsers/auto_configure_parser.py +13 -0
  26. claude_mpm/cli/parsers/base_parser.py +9 -0
  27. claude_mpm/cli/parsers/skills_parser.py +7 -0
  28. claude_mpm/cli/startup.py +133 -85
  29. claude_mpm/commands/mpm-agents-auto-configure.md +2 -2
  30. claude_mpm/commands/mpm-agents-list.md +2 -2
  31. claude_mpm/commands/mpm-config-view.md +2 -2
  32. claude_mpm/commands/mpm-help.md +3 -0
  33. claude_mpm/commands/{mpm-ticket-organize.md → mpm-organize.md} +4 -5
  34. claude_mpm/commands/mpm-postmortem.md +123 -0
  35. claude_mpm/commands/mpm-session-resume.md +2 -2
  36. claude_mpm/commands/mpm-ticket-view.md +2 -2
  37. claude_mpm/config/agent_presets.py +312 -82
  38. claude_mpm/config/agent_sources.py +27 -0
  39. claude_mpm/config/skill_presets.py +392 -0
  40. claude_mpm/constants.py +1 -0
  41. claude_mpm/core/claude_runner.py +2 -25
  42. claude_mpm/core/framework/loaders/agent_loader.py +8 -5
  43. claude_mpm/core/framework/loaders/file_loader.py +54 -101
  44. claude_mpm/core/interactive_session.py +19 -5
  45. claude_mpm/core/oneshot_session.py +16 -4
  46. claude_mpm/core/output_style_manager.py +173 -43
  47. claude_mpm/core/protocols/__init__.py +23 -0
  48. claude_mpm/core/protocols/runner_protocol.py +103 -0
  49. claude_mpm/core/protocols/session_protocol.py +131 -0
  50. claude_mpm/core/shared/singleton_manager.py +11 -4
  51. claude_mpm/core/socketio_pool.py +3 -3
  52. claude_mpm/core/system_context.py +38 -0
  53. claude_mpm/core/unified_agent_registry.py +134 -16
  54. claude_mpm/core/unified_config.py +22 -0
  55. claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-313.pyc +0 -0
  56. claude_mpm/hooks/claude_hooks/__pycache__/correlation_manager.cpython-313.pyc +0 -0
  57. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-313.pyc +0 -0
  58. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-313.pyc +0 -0
  59. claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-313.pyc +0 -0
  60. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-313.pyc +0 -0
  61. claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-313.pyc +0 -0
  62. claude_mpm/hooks/claude_hooks/correlation_manager.py +60 -0
  63. claude_mpm/hooks/claude_hooks/event_handlers.py +35 -2
  64. claude_mpm/hooks/claude_hooks/hook_handler.py +4 -0
  65. claude_mpm/hooks/claude_hooks/memory_integration.py +12 -1
  66. claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-313.pyc +0 -0
  67. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-313.pyc +0 -0
  68. claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-313.pyc +0 -0
  69. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-313.pyc +0 -0
  70. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-313.pyc +0 -0
  71. claude_mpm/hooks/claude_hooks/services/connection_manager.py +4 -0
  72. claude_mpm/models/agent_definition.py +7 -0
  73. claude_mpm/scripts/launch_monitor.py +93 -13
  74. claude_mpm/services/agents/agent_recommendation_service.py +279 -0
  75. claude_mpm/services/agents/cache_git_manager.py +621 -0
  76. claude_mpm/services/agents/deployment/agent_template_builder.py +3 -2
  77. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +110 -3
  78. claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +518 -55
  79. claude_mpm/services/agents/git_source_manager.py +20 -0
  80. claude_mpm/services/agents/sources/git_source_sync_service.py +45 -6
  81. claude_mpm/services/agents/toolchain_detector.py +6 -5
  82. claude_mpm/services/analysis/__init__.py +35 -0
  83. claude_mpm/services/analysis/clone_detector.py +1030 -0
  84. claude_mpm/services/analysis/postmortem_reporter.py +474 -0
  85. claude_mpm/services/analysis/postmortem_service.py +765 -0
  86. claude_mpm/services/command_deployment_service.py +106 -5
  87. claude_mpm/services/core/base.py +7 -2
  88. claude_mpm/services/diagnostics/checks/mcp_services_check.py +7 -15
  89. claude_mpm/services/event_bus/config.py +3 -1
  90. claude_mpm/services/git/git_operations_service.py +8 -8
  91. claude_mpm/services/mcp_config_manager.py +75 -145
  92. claude_mpm/services/mcp_service_verifier.py +6 -3
  93. claude_mpm/services/monitor/daemon.py +37 -10
  94. claude_mpm/services/monitor/daemon_manager.py +134 -21
  95. claude_mpm/services/monitor/server.py +225 -19
  96. claude_mpm/services/project/project_organizer.py +4 -0
  97. claude_mpm/services/runner_configuration_service.py +16 -3
  98. claude_mpm/services/session_management_service.py +16 -4
  99. claude_mpm/services/socketio/event_normalizer.py +15 -1
  100. claude_mpm/services/socketio/server/core.py +160 -21
  101. claude_mpm/services/version_control/git_operations.py +103 -0
  102. claude_mpm/utils/agent_filters.py +261 -0
  103. claude_mpm/utils/gitignore.py +3 -0
  104. claude_mpm/utils/migration.py +372 -0
  105. claude_mpm/utils/progress.py +5 -1
  106. {claude_mpm-5.0.2.dist-info → claude_mpm-5.4.3.dist-info}/METADATA +69 -84
  107. {claude_mpm-5.0.2.dist-info → claude_mpm-5.4.3.dist-info}/RECORD +112 -153
  108. {claude_mpm-5.0.2.dist-info → claude_mpm-5.4.3.dist-info}/entry_points.txt +0 -2
  109. claude_mpm/dashboard/analysis_runner.py +0 -455
  110. claude_mpm/dashboard/index.html +0 -13
  111. claude_mpm/dashboard/open_dashboard.py +0 -66
  112. claude_mpm/dashboard/static/css/activity.css +0 -1958
  113. claude_mpm/dashboard/static/css/connection-status.css +0 -370
  114. claude_mpm/dashboard/static/css/dashboard.css +0 -4701
  115. claude_mpm/dashboard/static/js/components/activity-tree.js +0 -1871
  116. claude_mpm/dashboard/static/js/components/agent-hierarchy.js +0 -777
  117. claude_mpm/dashboard/static/js/components/agent-inference.js +0 -956
  118. claude_mpm/dashboard/static/js/components/build-tracker.js +0 -333
  119. claude_mpm/dashboard/static/js/components/code-simple.js +0 -857
  120. claude_mpm/dashboard/static/js/components/connection-debug.js +0 -654
  121. claude_mpm/dashboard/static/js/components/diff-viewer.js +0 -891
  122. claude_mpm/dashboard/static/js/components/event-processor.js +0 -542
  123. claude_mpm/dashboard/static/js/components/event-viewer.js +0 -1155
  124. claude_mpm/dashboard/static/js/components/export-manager.js +0 -368
  125. claude_mpm/dashboard/static/js/components/file-change-tracker.js +0 -443
  126. claude_mpm/dashboard/static/js/components/file-change-viewer.js +0 -690
  127. claude_mpm/dashboard/static/js/components/file-tool-tracker.js +0 -724
  128. claude_mpm/dashboard/static/js/components/file-viewer.js +0 -580
  129. claude_mpm/dashboard/static/js/components/hud-library-loader.js +0 -211
  130. claude_mpm/dashboard/static/js/components/hud-manager.js +0 -671
  131. claude_mpm/dashboard/static/js/components/hud-visualizer.js +0 -1718
  132. claude_mpm/dashboard/static/js/components/module-viewer.js +0 -2764
  133. claude_mpm/dashboard/static/js/components/session-manager.js +0 -579
  134. claude_mpm/dashboard/static/js/components/socket-manager.js +0 -368
  135. claude_mpm/dashboard/static/js/components/ui-state-manager.js +0 -749
  136. claude_mpm/dashboard/static/js/components/unified-data-viewer.js +0 -1824
  137. claude_mpm/dashboard/static/js/components/working-directory.js +0 -920
  138. claude_mpm/dashboard/static/js/connection-manager.js +0 -536
  139. claude_mpm/dashboard/static/js/dashboard.js +0 -1914
  140. claude_mpm/dashboard/static/js/extension-error-handler.js +0 -164
  141. claude_mpm/dashboard/static/js/socket-client.js +0 -1474
  142. claude_mpm/dashboard/static/js/tab-isolation-fix.js +0 -185
  143. claude_mpm/dashboard/static/socket.io.min.js +0 -7
  144. claude_mpm/dashboard/static/socket.io.v4.8.1.backup.js +0 -7
  145. claude_mpm/dashboard/templates/code_simple.html +0 -153
  146. claude_mpm/dashboard/templates/index.html +0 -606
  147. claude_mpm/dashboard/test_dashboard.html +0 -372
  148. claude_mpm/scripts/mcp_server.py +0 -75
  149. claude_mpm/scripts/mcp_wrapper.py +0 -39
  150. claude_mpm/services/mcp_gateway/__init__.py +0 -159
  151. claude_mpm/services/mcp_gateway/auto_configure.py +0 -369
  152. claude_mpm/services/mcp_gateway/config/__init__.py +0 -17
  153. claude_mpm/services/mcp_gateway/config/config_loader.py +0 -296
  154. claude_mpm/services/mcp_gateway/config/config_schema.py +0 -243
  155. claude_mpm/services/mcp_gateway/config/configuration.py +0 -429
  156. claude_mpm/services/mcp_gateway/core/__init__.py +0 -43
  157. claude_mpm/services/mcp_gateway/core/base.py +0 -312
  158. claude_mpm/services/mcp_gateway/core/exceptions.py +0 -253
  159. claude_mpm/services/mcp_gateway/core/interfaces.py +0 -443
  160. claude_mpm/services/mcp_gateway/core/process_pool.py +0 -971
  161. claude_mpm/services/mcp_gateway/core/singleton_manager.py +0 -315
  162. claude_mpm/services/mcp_gateway/core/startup_verification.py +0 -316
  163. claude_mpm/services/mcp_gateway/main.py +0 -589
  164. claude_mpm/services/mcp_gateway/registry/__init__.py +0 -12
  165. claude_mpm/services/mcp_gateway/registry/service_registry.py +0 -412
  166. claude_mpm/services/mcp_gateway/registry/tool_registry.py +0 -489
  167. claude_mpm/services/mcp_gateway/server/__init__.py +0 -15
  168. claude_mpm/services/mcp_gateway/server/mcp_gateway.py +0 -414
  169. claude_mpm/services/mcp_gateway/server/stdio_handler.py +0 -372
  170. claude_mpm/services/mcp_gateway/server/stdio_server.py +0 -712
  171. claude_mpm/services/mcp_gateway/tools/__init__.py +0 -36
  172. claude_mpm/services/mcp_gateway/tools/base_adapter.py +0 -485
  173. claude_mpm/services/mcp_gateway/tools/document_summarizer.py +0 -789
  174. claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +0 -654
  175. claude_mpm/services/mcp_gateway/tools/health_check_tool.py +0 -456
  176. claude_mpm/services/mcp_gateway/tools/hello_world.py +0 -551
  177. claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +0 -555
  178. claude_mpm/services/mcp_gateway/utils/__init__.py +0 -14
  179. claude_mpm/services/mcp_gateway/utils/package_version_checker.py +0 -160
  180. claude_mpm/services/mcp_gateway/utils/update_preferences.py +0 -170
  181. /claude_mpm/agents/{OUTPUT_STYLE.md → CLAUDE_MPM_OUTPUT_STYLE.md} +0 -0
  182. {claude_mpm-5.0.2.dist-info → claude_mpm-5.4.3.dist-info}/WHEEL +0 -0
  183. {claude_mpm-5.0.2.dist-info → claude_mpm-5.4.3.dist-info}/licenses/LICENSE +0 -0
  184. {claude_mpm-5.0.2.dist-info → claude_mpm-5.4.3.dist-info}/top_level.txt +0 -0
@@ -1,8 +1,6 @@
1
1
  [console_scripts]
2
2
  claude-mpm = claude_mpm.cli:main
3
3
  claude-mpm-doctor = claude_mpm.scripts.mpm_doctor:main
4
- claude-mpm-mcp = claude_mpm.services.mcp_gateway.server.stdio_server:main_sync
5
- claude-mpm-mcp-wrapper = claude_mpm.scripts.mcp_wrapper:entry_point
6
4
  claude-mpm-monitor = claude_mpm.scripts.launch_monitor:main
7
5
  claude-mpm-socketio = claude_mpm.scripts.socketio_daemon:main
8
6
  claude-mpm-ticket = claude_mpm.cli.ticket_cli:main
@@ -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()