claude-mpm 5.0.9__py3-none-any.whl → 5.4.41__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 (263) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/__init__.py +4 -0
  3. claude_mpm/agents/BASE_AGENT.md +164 -0
  4. claude_mpm/agents/{PM_INSTRUCTIONS_TEACH.md → CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md} +721 -41
  5. claude_mpm/agents/MEMORY.md +1 -1
  6. claude_mpm/agents/PM_INSTRUCTIONS.md +468 -468
  7. claude_mpm/agents/WORKFLOW.md +5 -254
  8. claude_mpm/agents/agent_loader.py +13 -44
  9. claude_mpm/agents/base_agent.json +1 -1
  10. claude_mpm/agents/frontmatter_validator.py +70 -2
  11. claude_mpm/agents/templates/circuit-breakers.md +431 -45
  12. claude_mpm/cli/__init__.py +0 -1
  13. claude_mpm/cli/__main__.py +4 -0
  14. claude_mpm/cli/chrome_devtools_installer.py +175 -0
  15. claude_mpm/cli/commands/agent_state_manager.py +18 -27
  16. claude_mpm/cli/commands/agents.py +175 -37
  17. claude_mpm/cli/commands/auto_configure.py +723 -236
  18. claude_mpm/cli/commands/config.py +88 -2
  19. claude_mpm/cli/commands/configure.py +1262 -157
  20. claude_mpm/cli/commands/configure_agent_display.py +25 -6
  21. claude_mpm/cli/commands/mpm_init/core.py +225 -46
  22. claude_mpm/cli/commands/mpm_init/knowledge_extractor.py +481 -0
  23. claude_mpm/cli/commands/mpm_init/prompts.py +280 -0
  24. claude_mpm/cli/commands/postmortem.py +1 -1
  25. claude_mpm/cli/commands/profile.py +277 -0
  26. claude_mpm/cli/commands/skills.py +214 -189
  27. claude_mpm/cli/commands/summarize.py +413 -0
  28. claude_mpm/cli/executor.py +21 -3
  29. claude_mpm/cli/interactive/agent_wizard.py +85 -10
  30. claude_mpm/cli/parsers/agents_parser.py +54 -9
  31. claude_mpm/cli/parsers/auto_configure_parser.py +13 -138
  32. claude_mpm/cli/parsers/base_parser.py +12 -0
  33. claude_mpm/cli/parsers/config_parser.py +153 -83
  34. claude_mpm/cli/parsers/profile_parser.py +148 -0
  35. claude_mpm/cli/parsers/skills_parser.py +3 -2
  36. claude_mpm/cli/startup.py +879 -149
  37. claude_mpm/commands/mpm-config.md +28 -0
  38. claude_mpm/commands/mpm-doctor.md +9 -22
  39. claude_mpm/commands/mpm-help.md +5 -287
  40. claude_mpm/commands/mpm-init.md +81 -507
  41. claude_mpm/commands/mpm-monitor.md +15 -402
  42. claude_mpm/commands/mpm-organize.md +120 -0
  43. claude_mpm/commands/mpm-postmortem.md +6 -108
  44. claude_mpm/commands/mpm-session-resume.md +12 -363
  45. claude_mpm/commands/mpm-status.md +5 -69
  46. claude_mpm/commands/mpm-ticket-view.md +52 -495
  47. claude_mpm/commands/mpm-version.md +5 -107
  48. claude_mpm/config/agent_sources.py +27 -0
  49. claude_mpm/core/config.py +2 -4
  50. claude_mpm/core/framework/formatters/content_formatter.py +3 -13
  51. claude_mpm/core/framework/loaders/agent_loader.py +8 -5
  52. claude_mpm/core/framework/loaders/instruction_loader.py +52 -11
  53. claude_mpm/core/framework_loader.py +4 -2
  54. claude_mpm/core/logger.py +13 -0
  55. claude_mpm/core/optimized_startup.py +59 -0
  56. claude_mpm/core/output_style_manager.py +173 -43
  57. claude_mpm/core/shared/config_loader.py +1 -1
  58. claude_mpm/core/socketio_pool.py +3 -3
  59. claude_mpm/core/unified_agent_registry.py +134 -16
  60. claude_mpm/core/unified_config.py +22 -0
  61. claude_mpm/dashboard/static/svelte-build/_app/env.js +1 -0
  62. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.B_FtCwCQ.css +1 -0
  63. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.Cl_eSA4x.css +1 -0
  64. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BgChzWQ1.js +1 -0
  65. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CIXEwuWe.js +1 -0
  66. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CWc5urbQ.js +1 -0
  67. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DMkZpdF2.js +2 -0
  68. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DjhvlsAc.js +1 -0
  69. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/N4qtv3Hx.js +2 -0
  70. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/uj46x2Wr.js +1 -0
  71. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/app.DTL5mJO-.js +2 -0
  72. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.DzuEhzqh.js +1 -0
  73. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/0.CAGBuiOw.js +1 -0
  74. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/1.DFLC8jdE.js +1 -0
  75. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.DPvEihJJ.js +10 -0
  76. claude_mpm/dashboard/static/svelte-build/_app/version.json +1 -0
  77. claude_mpm/dashboard/static/svelte-build/favicon.svg +7 -0
  78. claude_mpm/dashboard/static/svelte-build/index.html +36 -0
  79. claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-311.pyc +0 -0
  80. claude_mpm/hooks/claude_hooks/__pycache__/correlation_manager.cpython-311.pyc +0 -0
  81. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-311.pyc +0 -0
  82. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-311.pyc +0 -0
  83. claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-311.pyc +0 -0
  84. claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-311.pyc +0 -0
  85. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-311.pyc +0 -0
  86. claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-311.pyc +0 -0
  87. claude_mpm/hooks/claude_hooks/correlation_manager.py +60 -0
  88. claude_mpm/hooks/claude_hooks/event_handlers.py +211 -78
  89. claude_mpm/hooks/claude_hooks/hook_handler.py +155 -1
  90. claude_mpm/hooks/claude_hooks/installer.py +33 -10
  91. claude_mpm/hooks/claude_hooks/memory_integration.py +28 -0
  92. claude_mpm/hooks/claude_hooks/response_tracking.py +2 -3
  93. claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-311.pyc +0 -0
  94. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager.cpython-311.pyc +0 -0
  95. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-311.pyc +0 -0
  96. claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-311.pyc +0 -0
  97. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-311.pyc +0 -0
  98. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-311.pyc +0 -0
  99. claude_mpm/hooks/claude_hooks/services/connection_manager.py +30 -6
  100. claude_mpm/hooks/memory_integration_hook.py +46 -1
  101. claude_mpm/init.py +63 -19
  102. claude_mpm/models/agent_definition.py +7 -0
  103. claude_mpm/models/git_repository.py +3 -3
  104. claude_mpm/scripts/claude-hook-handler.sh +58 -18
  105. claude_mpm/scripts/launch_monitor.py +93 -13
  106. claude_mpm/scripts/start_activity_logging.py +0 -0
  107. claude_mpm/services/agents/agent_builder.py +3 -3
  108. claude_mpm/services/agents/agent_recommendation_service.py +278 -0
  109. claude_mpm/services/agents/agent_review_service.py +280 -0
  110. claude_mpm/services/agents/cache_git_manager.py +6 -6
  111. claude_mpm/services/agents/deployment/agent_deployment.py +29 -7
  112. claude_mpm/services/agents/deployment/agent_discovery_service.py +4 -5
  113. claude_mpm/services/agents/deployment/agent_template_builder.py +5 -3
  114. claude_mpm/services/agents/deployment/agents_directory_resolver.py +2 -2
  115. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +320 -29
  116. claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +546 -68
  117. claude_mpm/services/agents/git_source_manager.py +36 -2
  118. claude_mpm/services/agents/loading/base_agent_manager.py +1 -13
  119. claude_mpm/services/agents/recommender.py +5 -3
  120. claude_mpm/services/agents/single_tier_deployment_service.py +2 -2
  121. claude_mpm/services/agents/sources/git_source_sync_service.py +13 -6
  122. claude_mpm/services/agents/startup_sync.py +22 -2
  123. claude_mpm/services/agents/toolchain_detector.py +10 -6
  124. claude_mpm/services/analysis/__init__.py +11 -1
  125. claude_mpm/services/analysis/clone_detector.py +1030 -0
  126. claude_mpm/services/command_deployment_service.py +81 -10
  127. claude_mpm/services/diagnostics/checks/agent_check.py +2 -2
  128. claude_mpm/services/diagnostics/checks/agent_sources_check.py +1 -1
  129. claude_mpm/services/event_bus/config.py +3 -1
  130. claude_mpm/services/git/git_operations_service.py +101 -16
  131. claude_mpm/services/monitor/daemon.py +9 -2
  132. claude_mpm/services/monitor/daemon_manager.py +39 -3
  133. claude_mpm/services/monitor/management/lifecycle.py +8 -1
  134. claude_mpm/services/monitor/server.py +698 -22
  135. claude_mpm/services/pm_skills_deployer.py +676 -0
  136. claude_mpm/services/profile_manager.py +331 -0
  137. claude_mpm/services/project/project_organizer.py +4 -0
  138. claude_mpm/services/self_upgrade_service.py +120 -12
  139. claude_mpm/services/skills/__init__.py +3 -0
  140. claude_mpm/services/skills/git_skill_source_manager.py +130 -2
  141. claude_mpm/services/skills/selective_skill_deployer.py +704 -0
  142. claude_mpm/services/skills/skill_to_agent_mapper.py +406 -0
  143. claude_mpm/services/skills_deployer.py +126 -9
  144. claude_mpm/services/socketio/dashboard_server.py +1 -0
  145. claude_mpm/services/socketio/event_normalizer.py +51 -6
  146. claude_mpm/services/socketio/server/core.py +386 -108
  147. claude_mpm/services/version_control/git_operations.py +103 -0
  148. claude_mpm/skills/skill_manager.py +92 -3
  149. claude_mpm/utils/agent_dependency_loader.py +14 -2
  150. claude_mpm/utils/agent_filters.py +17 -44
  151. claude_mpm/utils/gitignore.py +3 -0
  152. claude_mpm/utils/migration.py +4 -4
  153. claude_mpm/utils/robust_installer.py +47 -3
  154. {claude_mpm-5.0.9.dist-info → claude_mpm-5.4.41.dist-info}/METADATA +57 -87
  155. {claude_mpm-5.0.9.dist-info → claude_mpm-5.4.41.dist-info}/RECORD +160 -211
  156. claude_mpm-5.4.41.dist-info/entry_points.txt +5 -0
  157. claude_mpm-5.4.41.dist-info/licenses/LICENSE +94 -0
  158. claude_mpm-5.4.41.dist-info/licenses/LICENSE-FAQ.md +153 -0
  159. claude_mpm/agents/BASE_AGENT_TEMPLATE.md +0 -292
  160. claude_mpm/agents/BASE_DOCUMENTATION.md +0 -53
  161. claude_mpm/agents/BASE_OPS.md +0 -219
  162. claude_mpm/agents/BASE_PM.md +0 -480
  163. claude_mpm/agents/BASE_PROMPT_ENGINEER.md +0 -787
  164. claude_mpm/agents/BASE_QA.md +0 -167
  165. claude_mpm/agents/BASE_RESEARCH.md +0 -53
  166. claude_mpm/agents/base_agent_loader.py +0 -601
  167. claude_mpm/cli/commands/agents_detect.py +0 -380
  168. claude_mpm/cli/commands/agents_recommend.py +0 -309
  169. claude_mpm/cli/ticket_cli.py +0 -35
  170. claude_mpm/commands/mpm-agents-auto-configure.md +0 -278
  171. claude_mpm/commands/mpm-agents-detect.md +0 -177
  172. claude_mpm/commands/mpm-agents-list.md +0 -131
  173. claude_mpm/commands/mpm-agents-recommend.md +0 -223
  174. claude_mpm/commands/mpm-config-view.md +0 -150
  175. claude_mpm/commands/mpm-ticket-organize.md +0 -304
  176. claude_mpm/dashboard/analysis_runner.py +0 -455
  177. claude_mpm/dashboard/index.html +0 -13
  178. claude_mpm/dashboard/open_dashboard.py +0 -66
  179. claude_mpm/dashboard/static/css/activity.css +0 -1958
  180. claude_mpm/dashboard/static/css/connection-status.css +0 -370
  181. claude_mpm/dashboard/static/css/dashboard.css +0 -4701
  182. claude_mpm/dashboard/static/js/components/activity-tree.js +0 -1871
  183. claude_mpm/dashboard/static/js/components/agent-hierarchy.js +0 -777
  184. claude_mpm/dashboard/static/js/components/agent-inference.js +0 -956
  185. claude_mpm/dashboard/static/js/components/build-tracker.js +0 -333
  186. claude_mpm/dashboard/static/js/components/code-simple.js +0 -857
  187. claude_mpm/dashboard/static/js/components/connection-debug.js +0 -654
  188. claude_mpm/dashboard/static/js/components/diff-viewer.js +0 -891
  189. claude_mpm/dashboard/static/js/components/event-processor.js +0 -542
  190. claude_mpm/dashboard/static/js/components/event-viewer.js +0 -1155
  191. claude_mpm/dashboard/static/js/components/export-manager.js +0 -368
  192. claude_mpm/dashboard/static/js/components/file-change-tracker.js +0 -443
  193. claude_mpm/dashboard/static/js/components/file-change-viewer.js +0 -690
  194. claude_mpm/dashboard/static/js/components/file-tool-tracker.js +0 -724
  195. claude_mpm/dashboard/static/js/components/file-viewer.js +0 -580
  196. claude_mpm/dashboard/static/js/components/hud-library-loader.js +0 -211
  197. claude_mpm/dashboard/static/js/components/hud-manager.js +0 -671
  198. claude_mpm/dashboard/static/js/components/hud-visualizer.js +0 -1718
  199. claude_mpm/dashboard/static/js/components/module-viewer.js +0 -2764
  200. claude_mpm/dashboard/static/js/components/session-manager.js +0 -579
  201. claude_mpm/dashboard/static/js/components/socket-manager.js +0 -368
  202. claude_mpm/dashboard/static/js/components/ui-state-manager.js +0 -749
  203. claude_mpm/dashboard/static/js/components/unified-data-viewer.js +0 -1824
  204. claude_mpm/dashboard/static/js/components/working-directory.js +0 -920
  205. claude_mpm/dashboard/static/js/connection-manager.js +0 -536
  206. claude_mpm/dashboard/static/js/dashboard.js +0 -1914
  207. claude_mpm/dashboard/static/js/extension-error-handler.js +0 -164
  208. claude_mpm/dashboard/static/js/socket-client.js +0 -1474
  209. claude_mpm/dashboard/static/js/tab-isolation-fix.js +0 -185
  210. claude_mpm/dashboard/static/socket.io.min.js +0 -7
  211. claude_mpm/dashboard/static/socket.io.v4.8.1.backup.js +0 -7
  212. claude_mpm/dashboard/templates/code_simple.html +0 -153
  213. claude_mpm/dashboard/templates/index.html +0 -606
  214. claude_mpm/dashboard/test_dashboard.html +0 -372
  215. claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-313.pyc +0 -0
  216. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-313.pyc +0 -0
  217. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-313.pyc +0 -0
  218. claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-313.pyc +0 -0
  219. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-313.pyc +0 -0
  220. claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-313.pyc +0 -0
  221. claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-313.pyc +0 -0
  222. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-313.pyc +0 -0
  223. claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-313.pyc +0 -0
  224. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-313.pyc +0 -0
  225. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-313.pyc +0 -0
  226. claude_mpm/scripts/mcp_server.py +0 -75
  227. claude_mpm/scripts/mcp_wrapper.py +0 -39
  228. claude_mpm/services/mcp_gateway/__init__.py +0 -159
  229. claude_mpm/services/mcp_gateway/auto_configure.py +0 -369
  230. claude_mpm/services/mcp_gateway/config/__init__.py +0 -17
  231. claude_mpm/services/mcp_gateway/config/config_loader.py +0 -296
  232. claude_mpm/services/mcp_gateway/config/config_schema.py +0 -243
  233. claude_mpm/services/mcp_gateway/config/configuration.py +0 -429
  234. claude_mpm/services/mcp_gateway/core/__init__.py +0 -43
  235. claude_mpm/services/mcp_gateway/core/base.py +0 -312
  236. claude_mpm/services/mcp_gateway/core/exceptions.py +0 -253
  237. claude_mpm/services/mcp_gateway/core/interfaces.py +0 -443
  238. claude_mpm/services/mcp_gateway/core/process_pool.py +0 -977
  239. claude_mpm/services/mcp_gateway/core/singleton_manager.py +0 -315
  240. claude_mpm/services/mcp_gateway/core/startup_verification.py +0 -316
  241. claude_mpm/services/mcp_gateway/main.py +0 -589
  242. claude_mpm/services/mcp_gateway/registry/__init__.py +0 -12
  243. claude_mpm/services/mcp_gateway/registry/service_registry.py +0 -412
  244. claude_mpm/services/mcp_gateway/registry/tool_registry.py +0 -489
  245. claude_mpm/services/mcp_gateway/server/__init__.py +0 -15
  246. claude_mpm/services/mcp_gateway/server/mcp_gateway.py +0 -414
  247. claude_mpm/services/mcp_gateway/server/stdio_handler.py +0 -372
  248. claude_mpm/services/mcp_gateway/server/stdio_server.py +0 -712
  249. claude_mpm/services/mcp_gateway/tools/__init__.py +0 -36
  250. claude_mpm/services/mcp_gateway/tools/base_adapter.py +0 -485
  251. claude_mpm/services/mcp_gateway/tools/document_summarizer.py +0 -789
  252. claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +0 -654
  253. claude_mpm/services/mcp_gateway/tools/health_check_tool.py +0 -456
  254. claude_mpm/services/mcp_gateway/tools/hello_world.py +0 -551
  255. claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +0 -555
  256. claude_mpm/services/mcp_gateway/utils/__init__.py +0 -14
  257. claude_mpm/services/mcp_gateway/utils/package_version_checker.py +0 -160
  258. claude_mpm/services/mcp_gateway/utils/update_preferences.py +0 -170
  259. claude_mpm-5.0.9.dist-info/entry_points.txt +0 -10
  260. claude_mpm-5.0.9.dist-info/licenses/LICENSE +0 -21
  261. /claude_mpm/agents/{OUTPUT_STYLE.md → CLAUDE_MPM_OUTPUT_STYLE.md} +0 -0
  262. {claude_mpm-5.0.9.dist-info → claude_mpm-5.4.41.dist-info}/WHEEL +0 -0
  263. {claude_mpm-5.0.9.dist-info → claude_mpm-5.4.41.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()