claude-mpm 3.9.11__py3-none-any.whl → 4.0.4__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.
Files changed (434) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/__init__.py +2 -2
  3. claude_mpm/__main__.py +3 -2
  4. claude_mpm/agents/__init__.py +85 -79
  5. claude_mpm/agents/agent_loader.py +464 -1003
  6. claude_mpm/agents/agent_loader_integration.py +45 -45
  7. claude_mpm/agents/agents_metadata.py +29 -30
  8. claude_mpm/agents/async_agent_loader.py +156 -138
  9. claude_mpm/agents/base_agent.json +1 -1
  10. claude_mpm/agents/base_agent_loader.py +179 -151
  11. claude_mpm/agents/frontmatter_validator.py +229 -130
  12. claude_mpm/agents/schema/agent_schema.json +1 -1
  13. claude_mpm/agents/system_agent_config.py +213 -147
  14. claude_mpm/agents/templates/__init__.py +13 -13
  15. claude_mpm/agents/templates/code_analyzer.json +2 -2
  16. claude_mpm/agents/templates/data_engineer.json +1 -1
  17. claude_mpm/agents/templates/documentation.json +23 -11
  18. claude_mpm/agents/templates/engineer.json +22 -6
  19. claude_mpm/agents/templates/memory_manager.json +1 -1
  20. claude_mpm/agents/templates/ops.json +2 -2
  21. claude_mpm/agents/templates/project_organizer.json +1 -1
  22. claude_mpm/agents/templates/qa.json +1 -1
  23. claude_mpm/agents/templates/refactoring_engineer.json +222 -0
  24. claude_mpm/agents/templates/research.json +20 -14
  25. claude_mpm/agents/templates/security.json +1 -1
  26. claude_mpm/agents/templates/ticketing.json +2 -2
  27. claude_mpm/agents/templates/version_control.json +1 -1
  28. claude_mpm/agents/templates/web_qa.json +3 -1
  29. claude_mpm/agents/templates/web_ui.json +2 -2
  30. claude_mpm/cli/__init__.py +79 -51
  31. claude_mpm/cli/__main__.py +3 -2
  32. claude_mpm/cli/commands/__init__.py +20 -20
  33. claude_mpm/cli/commands/agents.py +279 -247
  34. claude_mpm/cli/commands/aggregate.py +138 -157
  35. claude_mpm/cli/commands/cleanup.py +147 -147
  36. claude_mpm/cli/commands/config.py +93 -76
  37. claude_mpm/cli/commands/info.py +17 -16
  38. claude_mpm/cli/commands/mcp.py +140 -905
  39. claude_mpm/cli/commands/mcp_command_router.py +139 -0
  40. claude_mpm/cli/commands/mcp_config_commands.py +20 -0
  41. claude_mpm/cli/commands/mcp_install_commands.py +20 -0
  42. claude_mpm/cli/commands/mcp_server_commands.py +175 -0
  43. claude_mpm/cli/commands/mcp_tool_commands.py +34 -0
  44. claude_mpm/cli/commands/memory.py +239 -203
  45. claude_mpm/cli/commands/monitor.py +330 -86
  46. claude_mpm/cli/commands/run.py +380 -429
  47. claude_mpm/cli/commands/run_config_checker.py +160 -0
  48. claude_mpm/cli/commands/socketio_monitor.py +235 -0
  49. claude_mpm/cli/commands/tickets.py +363 -220
  50. claude_mpm/cli/parser.py +24 -1156
  51. claude_mpm/cli/parsers/__init__.py +29 -0
  52. claude_mpm/cli/parsers/agents_parser.py +136 -0
  53. claude_mpm/cli/parsers/base_parser.py +331 -0
  54. claude_mpm/cli/parsers/config_parser.py +85 -0
  55. claude_mpm/cli/parsers/mcp_parser.py +152 -0
  56. claude_mpm/cli/parsers/memory_parser.py +138 -0
  57. claude_mpm/cli/parsers/monitor_parser.py +124 -0
  58. claude_mpm/cli/parsers/run_parser.py +147 -0
  59. claude_mpm/cli/parsers/tickets_parser.py +203 -0
  60. claude_mpm/cli/ticket_cli.py +7 -3
  61. claude_mpm/cli/utils.py +55 -37
  62. claude_mpm/cli_module/__init__.py +6 -6
  63. claude_mpm/cli_module/args.py +188 -140
  64. claude_mpm/cli_module/commands.py +79 -70
  65. claude_mpm/cli_module/migration_example.py +38 -60
  66. claude_mpm/config/__init__.py +32 -25
  67. claude_mpm/config/agent_config.py +151 -119
  68. claude_mpm/config/experimental_features.py +71 -73
  69. claude_mpm/config/paths.py +94 -208
  70. claude_mpm/config/socketio_config.py +84 -73
  71. claude_mpm/constants.py +35 -18
  72. claude_mpm/core/__init__.py +9 -6
  73. claude_mpm/core/agent_name_normalizer.py +68 -71
  74. claude_mpm/core/agent_registry.py +372 -521
  75. claude_mpm/core/agent_session_manager.py +74 -63
  76. claude_mpm/core/base_service.py +116 -87
  77. claude_mpm/core/cache.py +119 -153
  78. claude_mpm/core/claude_runner.py +425 -1120
  79. claude_mpm/core/config.py +263 -168
  80. claude_mpm/core/config_aliases.py +69 -61
  81. claude_mpm/core/config_constants.py +292 -0
  82. claude_mpm/core/constants.py +57 -99
  83. claude_mpm/core/container.py +211 -178
  84. claude_mpm/core/exceptions.py +233 -89
  85. claude_mpm/core/factories.py +92 -54
  86. claude_mpm/core/framework_loader.py +378 -220
  87. claude_mpm/core/hook_manager.py +198 -83
  88. claude_mpm/core/hook_performance_config.py +136 -0
  89. claude_mpm/core/injectable_service.py +61 -55
  90. claude_mpm/core/interactive_session.py +165 -155
  91. claude_mpm/core/interfaces.py +221 -195
  92. claude_mpm/core/lazy.py +96 -96
  93. claude_mpm/core/logger.py +133 -107
  94. claude_mpm/core/logging_config.py +185 -157
  95. claude_mpm/core/minimal_framework_loader.py +20 -15
  96. claude_mpm/core/mixins.py +30 -29
  97. claude_mpm/core/oneshot_session.py +215 -181
  98. claude_mpm/core/optimized_agent_loader.py +134 -138
  99. claude_mpm/core/optimized_startup.py +159 -157
  100. claude_mpm/core/pm_hook_interceptor.py +85 -72
  101. claude_mpm/core/service_registry.py +103 -101
  102. claude_mpm/core/session_manager.py +97 -87
  103. claude_mpm/core/socketio_pool.py +212 -158
  104. claude_mpm/core/tool_access_control.py +58 -51
  105. claude_mpm/core/types.py +46 -24
  106. claude_mpm/core/typing_utils.py +166 -82
  107. claude_mpm/core/unified_agent_registry.py +721 -0
  108. claude_mpm/core/unified_config.py +550 -0
  109. claude_mpm/core/unified_paths.py +549 -0
  110. claude_mpm/dashboard/index.html +1 -1
  111. claude_mpm/dashboard/open_dashboard.py +51 -17
  112. claude_mpm/dashboard/static/built/components/agent-inference.js +2 -0
  113. claude_mpm/dashboard/static/built/components/event-processor.js +2 -0
  114. claude_mpm/dashboard/static/built/components/event-viewer.js +2 -0
  115. claude_mpm/dashboard/static/built/components/export-manager.js +2 -0
  116. claude_mpm/dashboard/static/built/components/file-tool-tracker.js +2 -0
  117. claude_mpm/dashboard/static/built/components/hud-library-loader.js +2 -0
  118. claude_mpm/dashboard/static/built/components/hud-manager.js +2 -0
  119. claude_mpm/dashboard/static/built/components/hud-visualizer.js +2 -0
  120. claude_mpm/dashboard/static/built/components/module-viewer.js +2 -0
  121. claude_mpm/dashboard/static/built/components/session-manager.js +2 -0
  122. claude_mpm/dashboard/static/built/components/socket-manager.js +2 -0
  123. claude_mpm/dashboard/static/built/components/ui-state-manager.js +2 -0
  124. claude_mpm/dashboard/static/built/components/working-directory.js +2 -0
  125. claude_mpm/dashboard/static/built/dashboard.js +2 -0
  126. claude_mpm/dashboard/static/built/socket-client.js +2 -0
  127. claude_mpm/dashboard/static/css/dashboard.css +27 -8
  128. claude_mpm/dashboard/static/dist/components/agent-inference.js +2 -0
  129. claude_mpm/dashboard/static/dist/components/event-processor.js +2 -0
  130. claude_mpm/dashboard/static/dist/components/event-viewer.js +2 -0
  131. claude_mpm/dashboard/static/dist/components/export-manager.js +2 -0
  132. claude_mpm/dashboard/static/dist/components/file-tool-tracker.js +2 -0
  133. claude_mpm/dashboard/static/dist/components/hud-library-loader.js +2 -0
  134. claude_mpm/dashboard/static/dist/components/hud-manager.js +2 -0
  135. claude_mpm/dashboard/static/dist/components/hud-visualizer.js +2 -0
  136. claude_mpm/dashboard/static/dist/components/module-viewer.js +2 -0
  137. claude_mpm/dashboard/static/dist/components/session-manager.js +2 -0
  138. claude_mpm/dashboard/static/dist/components/socket-manager.js +2 -0
  139. claude_mpm/dashboard/static/dist/components/ui-state-manager.js +2 -0
  140. claude_mpm/dashboard/static/dist/components/working-directory.js +2 -0
  141. claude_mpm/dashboard/static/dist/dashboard.js +2 -0
  142. claude_mpm/dashboard/static/dist/socket-client.js +2 -0
  143. claude_mpm/dashboard/static/js/components/agent-inference.js +80 -76
  144. claude_mpm/dashboard/static/js/components/event-processor.js +71 -67
  145. claude_mpm/dashboard/static/js/components/event-viewer.js +93 -72
  146. claude_mpm/dashboard/static/js/components/export-manager.js +31 -28
  147. claude_mpm/dashboard/static/js/components/file-tool-tracker.js +110 -96
  148. claude_mpm/dashboard/static/js/components/hud-library-loader.js +11 -11
  149. claude_mpm/dashboard/static/js/components/hud-manager.js +73 -73
  150. claude_mpm/dashboard/static/js/components/hud-visualizer.js +163 -163
  151. claude_mpm/dashboard/static/js/components/module-viewer.js +305 -233
  152. claude_mpm/dashboard/static/js/components/session-manager.js +32 -29
  153. claude_mpm/dashboard/static/js/components/socket-manager.js +27 -20
  154. claude_mpm/dashboard/static/js/components/ui-state-manager.js +21 -18
  155. claude_mpm/dashboard/static/js/components/working-directory.js +74 -71
  156. claude_mpm/dashboard/static/js/dashboard.js +178 -453
  157. claude_mpm/dashboard/static/js/extension-error-handler.js +164 -0
  158. claude_mpm/dashboard/static/js/socket-client.js +133 -53
  159. claude_mpm/dashboard/templates/index.html +40 -50
  160. claude_mpm/experimental/cli_enhancements.py +60 -58
  161. claude_mpm/generators/__init__.py +1 -1
  162. claude_mpm/generators/agent_profile_generator.py +75 -65
  163. claude_mpm/hooks/__init__.py +1 -1
  164. claude_mpm/hooks/base_hook.py +33 -28
  165. claude_mpm/hooks/claude_hooks/__init__.py +1 -1
  166. claude_mpm/hooks/claude_hooks/connection_pool.py +120 -0
  167. claude_mpm/hooks/claude_hooks/event_handlers.py +743 -0
  168. claude_mpm/hooks/claude_hooks/hook_handler.py +415 -1331
  169. claude_mpm/hooks/claude_hooks/hook_wrapper.sh +4 -4
  170. claude_mpm/hooks/claude_hooks/memory_integration.py +221 -0
  171. claude_mpm/hooks/claude_hooks/response_tracking.py +348 -0
  172. claude_mpm/hooks/claude_hooks/tool_analysis.py +230 -0
  173. claude_mpm/hooks/memory_integration_hook.py +140 -100
  174. claude_mpm/hooks/tool_call_interceptor.py +89 -76
  175. claude_mpm/hooks/validation_hooks.py +57 -49
  176. claude_mpm/init.py +145 -121
  177. claude_mpm/models/__init__.py +9 -9
  178. claude_mpm/models/agent_definition.py +33 -23
  179. claude_mpm/models/agent_session.py +228 -200
  180. claude_mpm/scripts/__init__.py +1 -1
  181. claude_mpm/scripts/socketio_daemon.py +192 -75
  182. claude_mpm/scripts/socketio_server_manager.py +328 -0
  183. claude_mpm/scripts/start_activity_logging.py +25 -22
  184. claude_mpm/services/__init__.py +68 -43
  185. claude_mpm/services/agent_capabilities_service.py +271 -0
  186. claude_mpm/services/agents/__init__.py +23 -32
  187. claude_mpm/services/agents/deployment/__init__.py +3 -3
  188. claude_mpm/services/agents/deployment/agent_config_provider.py +310 -0
  189. claude_mpm/services/agents/deployment/agent_configuration_manager.py +359 -0
  190. claude_mpm/services/agents/deployment/agent_definition_factory.py +84 -0
  191. claude_mpm/services/agents/deployment/agent_deployment.py +415 -2113
  192. claude_mpm/services/agents/deployment/agent_discovery_service.py +387 -0
  193. claude_mpm/services/agents/deployment/agent_environment_manager.py +293 -0
  194. claude_mpm/services/agents/deployment/agent_filesystem_manager.py +387 -0
  195. claude_mpm/services/agents/deployment/agent_format_converter.py +453 -0
  196. claude_mpm/services/agents/deployment/agent_frontmatter_validator.py +161 -0
  197. claude_mpm/services/agents/deployment/agent_lifecycle_manager.py +345 -495
  198. claude_mpm/services/agents/deployment/agent_metrics_collector.py +279 -0
  199. claude_mpm/services/agents/deployment/agent_restore_handler.py +88 -0
  200. claude_mpm/services/agents/deployment/agent_template_builder.py +406 -0
  201. claude_mpm/services/agents/deployment/agent_validator.py +352 -0
  202. claude_mpm/services/agents/deployment/agent_version_manager.py +313 -0
  203. claude_mpm/services/agents/deployment/agent_versioning.py +6 -9
  204. claude_mpm/services/agents/deployment/agents_directory_resolver.py +79 -0
  205. claude_mpm/services/agents/deployment/async_agent_deployment.py +298 -234
  206. claude_mpm/services/agents/deployment/config/__init__.py +13 -0
  207. claude_mpm/services/agents/deployment/config/deployment_config.py +182 -0
  208. claude_mpm/services/agents/deployment/config/deployment_config_manager.py +200 -0
  209. claude_mpm/services/agents/deployment/deployment_config_loader.py +54 -0
  210. claude_mpm/services/agents/deployment/deployment_type_detector.py +124 -0
  211. claude_mpm/services/agents/deployment/facade/__init__.py +18 -0
  212. claude_mpm/services/agents/deployment/facade/async_deployment_executor.py +159 -0
  213. claude_mpm/services/agents/deployment/facade/deployment_executor.py +73 -0
  214. claude_mpm/services/agents/deployment/facade/deployment_facade.py +270 -0
  215. claude_mpm/services/agents/deployment/facade/sync_deployment_executor.py +178 -0
  216. claude_mpm/services/agents/deployment/interface_adapter.py +227 -0
  217. claude_mpm/services/agents/deployment/lifecycle_health_checker.py +85 -0
  218. claude_mpm/services/agents/deployment/lifecycle_performance_tracker.py +100 -0
  219. claude_mpm/services/agents/deployment/pipeline/__init__.py +32 -0
  220. claude_mpm/services/agents/deployment/pipeline/pipeline_builder.py +158 -0
  221. claude_mpm/services/agents/deployment/pipeline/pipeline_context.py +159 -0
  222. claude_mpm/services/agents/deployment/pipeline/pipeline_executor.py +169 -0
  223. claude_mpm/services/agents/deployment/pipeline/steps/__init__.py +19 -0
  224. claude_mpm/services/agents/deployment/pipeline/steps/agent_processing_step.py +195 -0
  225. claude_mpm/services/agents/deployment/pipeline/steps/base_step.py +119 -0
  226. claude_mpm/services/agents/deployment/pipeline/steps/configuration_step.py +79 -0
  227. claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +90 -0
  228. claude_mpm/services/agents/deployment/pipeline/steps/validation_step.py +100 -0
  229. claude_mpm/services/agents/deployment/processors/__init__.py +15 -0
  230. claude_mpm/services/agents/deployment/processors/agent_deployment_context.py +98 -0
  231. claude_mpm/services/agents/deployment/processors/agent_deployment_result.py +235 -0
  232. claude_mpm/services/agents/deployment/processors/agent_processor.py +258 -0
  233. claude_mpm/services/agents/deployment/refactored_agent_deployment_service.py +318 -0
  234. claude_mpm/services/agents/deployment/results/__init__.py +13 -0
  235. claude_mpm/services/agents/deployment/results/deployment_metrics.py +200 -0
  236. claude_mpm/services/agents/deployment/results/deployment_result_builder.py +249 -0
  237. claude_mpm/services/agents/deployment/strategies/__init__.py +25 -0
  238. claude_mpm/services/agents/deployment/strategies/base_strategy.py +119 -0
  239. claude_mpm/services/agents/deployment/strategies/project_strategy.py +150 -0
  240. claude_mpm/services/agents/deployment/strategies/strategy_selector.py +117 -0
  241. claude_mpm/services/agents/deployment/strategies/system_strategy.py +116 -0
  242. claude_mpm/services/agents/deployment/strategies/user_strategy.py +137 -0
  243. claude_mpm/services/agents/deployment/system_instructions_deployer.py +108 -0
  244. claude_mpm/services/agents/deployment/validation/__init__.py +19 -0
  245. claude_mpm/services/agents/deployment/validation/agent_validator.py +323 -0
  246. claude_mpm/services/agents/deployment/validation/deployment_validator.py +238 -0
  247. claude_mpm/services/agents/deployment/validation/template_validator.py +299 -0
  248. claude_mpm/services/agents/deployment/validation/validation_result.py +226 -0
  249. claude_mpm/services/agents/loading/__init__.py +2 -2
  250. claude_mpm/services/agents/loading/agent_profile_loader.py +259 -229
  251. claude_mpm/services/agents/loading/base_agent_manager.py +90 -81
  252. claude_mpm/services/agents/loading/framework_agent_loader.py +154 -129
  253. claude_mpm/services/agents/management/__init__.py +2 -2
  254. claude_mpm/services/agents/management/agent_capabilities_generator.py +72 -58
  255. claude_mpm/services/agents/management/agent_management_service.py +209 -156
  256. claude_mpm/services/agents/memory/__init__.py +9 -6
  257. claude_mpm/services/agents/memory/agent_memory_manager.py +218 -1152
  258. claude_mpm/services/agents/memory/agent_persistence_service.py +20 -16
  259. claude_mpm/services/agents/memory/analyzer.py +430 -0
  260. claude_mpm/services/agents/memory/content_manager.py +376 -0
  261. claude_mpm/services/agents/memory/template_generator.py +468 -0
  262. claude_mpm/services/agents/registry/__init__.py +7 -10
  263. claude_mpm/services/agents/registry/deployed_agent_discovery.py +122 -97
  264. claude_mpm/services/agents/registry/modification_tracker.py +351 -285
  265. claude_mpm/services/async_session_logger.py +187 -153
  266. claude_mpm/services/claude_session_logger.py +87 -72
  267. claude_mpm/services/command_handler_service.py +217 -0
  268. claude_mpm/services/communication/__init__.py +3 -2
  269. claude_mpm/services/core/__init__.py +50 -97
  270. claude_mpm/services/core/base.py +60 -53
  271. claude_mpm/services/core/interfaces/__init__.py +188 -0
  272. claude_mpm/services/core/interfaces/agent.py +351 -0
  273. claude_mpm/services/core/interfaces/communication.py +343 -0
  274. claude_mpm/services/core/interfaces/infrastructure.py +413 -0
  275. claude_mpm/services/core/interfaces/service.py +434 -0
  276. claude_mpm/services/core/interfaces.py +19 -944
  277. claude_mpm/services/event_aggregator.py +208 -170
  278. claude_mpm/services/exceptions.py +387 -308
  279. claude_mpm/services/framework_claude_md_generator/__init__.py +75 -79
  280. claude_mpm/services/framework_claude_md_generator/content_assembler.py +69 -60
  281. claude_mpm/services/framework_claude_md_generator/content_validator.py +65 -61
  282. claude_mpm/services/framework_claude_md_generator/deployment_manager.py +68 -49
  283. claude_mpm/services/framework_claude_md_generator/section_generators/__init__.py +34 -34
  284. claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +25 -22
  285. claude_mpm/services/framework_claude_md_generator/section_generators/claude_pm_init.py +10 -10
  286. claude_mpm/services/framework_claude_md_generator/section_generators/core_responsibilities.py +4 -3
  287. claude_mpm/services/framework_claude_md_generator/section_generators/delegation_constraints.py +4 -3
  288. claude_mpm/services/framework_claude_md_generator/section_generators/environment_config.py +4 -3
  289. claude_mpm/services/framework_claude_md_generator/section_generators/footer.py +6 -5
  290. claude_mpm/services/framework_claude_md_generator/section_generators/header.py +8 -7
  291. claude_mpm/services/framework_claude_md_generator/section_generators/orchestration_principles.py +4 -3
  292. claude_mpm/services/framework_claude_md_generator/section_generators/role_designation.py +6 -5
  293. claude_mpm/services/framework_claude_md_generator/section_generators/subprocess_validation.py +9 -8
  294. claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +4 -3
  295. claude_mpm/services/framework_claude_md_generator/section_generators/troubleshooting.py +5 -4
  296. claude_mpm/services/framework_claude_md_generator/section_manager.py +28 -27
  297. claude_mpm/services/framework_claude_md_generator/version_manager.py +30 -28
  298. claude_mpm/services/hook_service.py +106 -114
  299. claude_mpm/services/infrastructure/__init__.py +7 -5
  300. claude_mpm/services/infrastructure/context_preservation.py +233 -199
  301. claude_mpm/services/infrastructure/daemon_manager.py +279 -0
  302. claude_mpm/services/infrastructure/logging.py +83 -76
  303. claude_mpm/services/infrastructure/monitoring.py +547 -404
  304. claude_mpm/services/mcp_gateway/__init__.py +30 -13
  305. claude_mpm/services/mcp_gateway/config/__init__.py +2 -2
  306. claude_mpm/services/mcp_gateway/config/config_loader.py +61 -56
  307. claude_mpm/services/mcp_gateway/config/config_schema.py +50 -41
  308. claude_mpm/services/mcp_gateway/config/configuration.py +82 -75
  309. claude_mpm/services/mcp_gateway/core/__init__.py +13 -20
  310. claude_mpm/services/mcp_gateway/core/base.py +80 -67
  311. claude_mpm/services/mcp_gateway/core/exceptions.py +60 -46
  312. claude_mpm/services/mcp_gateway/core/interfaces.py +87 -84
  313. claude_mpm/services/mcp_gateway/main.py +287 -137
  314. claude_mpm/services/mcp_gateway/registry/__init__.py +1 -1
  315. claude_mpm/services/mcp_gateway/registry/service_registry.py +97 -94
  316. claude_mpm/services/mcp_gateway/registry/tool_registry.py +135 -126
  317. claude_mpm/services/mcp_gateway/server/__init__.py +2 -2
  318. claude_mpm/services/mcp_gateway/server/mcp_gateway.py +105 -110
  319. claude_mpm/services/mcp_gateway/server/stdio_handler.py +105 -107
  320. claude_mpm/services/mcp_gateway/server/stdio_server.py +691 -0
  321. claude_mpm/services/mcp_gateway/tools/__init__.py +4 -2
  322. claude_mpm/services/mcp_gateway/tools/base_adapter.py +109 -119
  323. claude_mpm/services/mcp_gateway/tools/document_summarizer.py +283 -215
  324. claude_mpm/services/mcp_gateway/tools/hello_world.py +122 -120
  325. claude_mpm/services/mcp_gateway/tools/ticket_tools.py +652 -0
  326. claude_mpm/services/mcp_gateway/tools/unified_ticket_tool.py +606 -0
  327. claude_mpm/services/memory/__init__.py +2 -2
  328. claude_mpm/services/memory/builder.py +451 -362
  329. claude_mpm/services/memory/cache/__init__.py +2 -2
  330. claude_mpm/services/memory/cache/shared_prompt_cache.py +232 -194
  331. claude_mpm/services/memory/cache/simple_cache.py +107 -93
  332. claude_mpm/services/memory/indexed_memory.py +195 -193
  333. claude_mpm/services/memory/optimizer.py +267 -234
  334. claude_mpm/services/memory/router.py +571 -263
  335. claude_mpm/services/memory_hook_service.py +237 -0
  336. claude_mpm/services/port_manager.py +575 -0
  337. claude_mpm/services/project/__init__.py +3 -3
  338. claude_mpm/services/project/analyzer.py +451 -305
  339. claude_mpm/services/project/registry.py +262 -240
  340. claude_mpm/services/recovery_manager.py +287 -231
  341. claude_mpm/services/response_tracker.py +87 -67
  342. claude_mpm/services/runner_configuration_service.py +587 -0
  343. claude_mpm/services/session_management_service.py +304 -0
  344. claude_mpm/services/socketio/__init__.py +4 -4
  345. claude_mpm/services/socketio/client_proxy.py +174 -0
  346. claude_mpm/services/socketio/handlers/__init__.py +3 -3
  347. claude_mpm/services/socketio/handlers/base.py +44 -30
  348. claude_mpm/services/socketio/handlers/connection.py +166 -64
  349. claude_mpm/services/socketio/handlers/file.py +123 -108
  350. claude_mpm/services/socketio/handlers/git.py +607 -373
  351. claude_mpm/services/socketio/handlers/hook.py +185 -0
  352. claude_mpm/services/socketio/handlers/memory.py +4 -4
  353. claude_mpm/services/socketio/handlers/project.py +4 -4
  354. claude_mpm/services/socketio/handlers/registry.py +53 -38
  355. claude_mpm/services/socketio/server/__init__.py +18 -0
  356. claude_mpm/services/socketio/server/broadcaster.py +252 -0
  357. claude_mpm/services/socketio/server/core.py +399 -0
  358. claude_mpm/services/socketio/server/main.py +323 -0
  359. claude_mpm/services/socketio_client_manager.py +160 -133
  360. claude_mpm/services/socketio_server.py +36 -1885
  361. claude_mpm/services/subprocess_launcher_service.py +316 -0
  362. claude_mpm/services/system_instructions_service.py +258 -0
  363. claude_mpm/services/ticket_manager.py +19 -533
  364. claude_mpm/services/utility_service.py +285 -0
  365. claude_mpm/services/version_control/__init__.py +18 -21
  366. claude_mpm/services/version_control/branch_strategy.py +20 -10
  367. claude_mpm/services/version_control/conflict_resolution.py +37 -13
  368. claude_mpm/services/version_control/git_operations.py +52 -21
  369. claude_mpm/services/version_control/semantic_versioning.py +92 -53
  370. claude_mpm/services/version_control/version_parser.py +145 -125
  371. claude_mpm/services/version_service.py +270 -0
  372. claude_mpm/storage/__init__.py +2 -2
  373. claude_mpm/storage/state_storage.py +177 -181
  374. claude_mpm/ticket_wrapper.py +2 -2
  375. claude_mpm/utils/__init__.py +2 -2
  376. claude_mpm/utils/agent_dependency_loader.py +453 -243
  377. claude_mpm/utils/config_manager.py +157 -118
  378. claude_mpm/utils/console.py +1 -1
  379. claude_mpm/utils/dependency_cache.py +102 -107
  380. claude_mpm/utils/dependency_manager.py +52 -47
  381. claude_mpm/utils/dependency_strategies.py +131 -96
  382. claude_mpm/utils/environment_context.py +110 -102
  383. claude_mpm/utils/error_handler.py +75 -55
  384. claude_mpm/utils/file_utils.py +80 -67
  385. claude_mpm/utils/framework_detection.py +12 -11
  386. claude_mpm/utils/import_migration_example.py +12 -60
  387. claude_mpm/utils/imports.py +48 -45
  388. claude_mpm/utils/path_operations.py +100 -93
  389. claude_mpm/utils/robust_installer.py +172 -164
  390. claude_mpm/utils/session_logging.py +30 -23
  391. claude_mpm/utils/subprocess_utils.py +99 -61
  392. claude_mpm/validation/__init__.py +1 -1
  393. claude_mpm/validation/agent_validator.py +151 -111
  394. claude_mpm/validation/frontmatter_validator.py +92 -71
  395. {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.4.dist-info}/METADATA +90 -22
  396. claude_mpm-4.0.4.dist-info/RECORD +417 -0
  397. {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.4.dist-info}/entry_points.txt +1 -0
  398. {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.4.dist-info}/licenses/LICENSE +1 -1
  399. claude_mpm/cli/commands/run_guarded.py +0 -511
  400. claude_mpm/config/memory_guardian_config.py +0 -325
  401. claude_mpm/config/memory_guardian_yaml.py +0 -335
  402. claude_mpm/core/config_paths.py +0 -150
  403. claude_mpm/core/memory_aware_runner.py +0 -353
  404. claude_mpm/dashboard/static/js/dashboard-original.js +0 -4134
  405. claude_mpm/deployment_paths.py +0 -261
  406. claude_mpm/hooks/claude_hooks/hook_handler_fixed.py +0 -454
  407. claude_mpm/models/state_models.py +0 -433
  408. claude_mpm/services/agent/__init__.py +0 -24
  409. claude_mpm/services/agent/deployment.py +0 -2548
  410. claude_mpm/services/agent/management.py +0 -598
  411. claude_mpm/services/agent/registry.py +0 -813
  412. claude_mpm/services/agents/registry/agent_registry.py +0 -813
  413. claude_mpm/services/communication/socketio.py +0 -1935
  414. claude_mpm/services/communication/websocket.py +0 -479
  415. claude_mpm/services/framework_claude_md_generator.py +0 -624
  416. claude_mpm/services/health_monitor.py +0 -893
  417. claude_mpm/services/infrastructure/graceful_degradation.py +0 -616
  418. claude_mpm/services/infrastructure/health_monitor.py +0 -775
  419. claude_mpm/services/infrastructure/memory_dashboard.py +0 -479
  420. claude_mpm/services/infrastructure/memory_guardian.py +0 -944
  421. claude_mpm/services/infrastructure/restart_protection.py +0 -642
  422. claude_mpm/services/infrastructure/state_manager.py +0 -774
  423. claude_mpm/services/mcp_gateway/manager.py +0 -334
  424. claude_mpm/services/optimized_hook_service.py +0 -542
  425. claude_mpm/services/project_analyzer.py +0 -864
  426. claude_mpm/services/project_registry.py +0 -608
  427. claude_mpm/services/standalone_socketio_server.py +0 -1300
  428. claude_mpm/services/ticket_manager_di.py +0 -318
  429. claude_mpm/services/ticketing_service_original.py +0 -510
  430. claude_mpm/utils/paths.py +0 -395
  431. claude_mpm/utils/platform_memory.py +0 -524
  432. claude_mpm-3.9.11.dist-info/RECORD +0 -306
  433. {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.4.dist-info}/WHEEL +0 -0
  434. {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.4.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,575 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Port Manager for SocketIO Server
4
+
5
+ Handles dynamic port selection, instance detection, and port availability checking.
6
+ Ensures only one instance runs per port and provides fallback port selection.
7
+ """
8
+
9
+ import json
10
+ import os
11
+ import signal
12
+ import socket
13
+ import subprocess
14
+ import time
15
+ from pathlib import Path
16
+ from typing import Dict, List, Optional, Tuple, NamedTuple
17
+
18
+ import psutil
19
+
20
+ from ..core.logging_config import get_logger
21
+
22
+
23
+ class ProcessInfo(NamedTuple):
24
+ """Information about a process using a port."""
25
+ pid: int
26
+ name: str
27
+ cmdline: str
28
+ is_ours: bool
29
+ is_debug: bool
30
+ is_daemon: bool
31
+
32
+
33
+ class PortManager:
34
+ """Manages port allocation and instance detection for SocketIO servers."""
35
+
36
+ # Port range for SocketIO servers
37
+ PORT_RANGE = range(8765, 8786) # 8765-8785 (21 ports)
38
+ DEFAULT_PORT = 8765
39
+
40
+ def __init__(self, project_root: Optional[Path] = None):
41
+ self.logger = get_logger(__name__ + ".PortManager")
42
+ self.project_root = project_root or Path.cwd()
43
+ self.state_dir = self.project_root / ".claude-mpm"
44
+ self.state_dir.mkdir(exist_ok=True)
45
+ self.instances_file = self.state_dir / "socketio-instances.json"
46
+
47
+ def is_port_available(self, port: int) -> bool:
48
+ """Check if a port is available for binding."""
49
+ try:
50
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
51
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
52
+ result = sock.bind(("localhost", port))
53
+ return True
54
+ except OSError:
55
+ return False
56
+
57
+ def get_process_on_port(self, port: int) -> Optional[ProcessInfo]:
58
+ """Get information about the process using a specific port.
59
+
60
+ WHY: We need to identify what process is using a port to make intelligent
61
+ decisions about whether we can reclaim it (our debug scripts) or must
62
+ avoid it (external processes or our daemons).
63
+
64
+ Returns:
65
+ ProcessInfo with details about the process, or None if port is free
66
+ """
67
+ try:
68
+ # First try using lsof as it's more reliable for port detection
69
+ try:
70
+ result = subprocess.run(
71
+ ['lsof', '-i', f':{port}', '-sTCP:LISTEN', '-t'],
72
+ capture_output=True,
73
+ text=True,
74
+ timeout=2
75
+ )
76
+ if result.returncode == 0 and result.stdout.strip():
77
+ # Get the PID from lsof output
78
+ pid = int(result.stdout.strip().split()[0])
79
+ try:
80
+ process = psutil.Process(pid)
81
+ cmdline = ' '.join(process.cmdline())
82
+
83
+ # Determine if this is our process and what type
84
+ is_ours = self._is_our_process(pid, cmdline)
85
+ is_debug = self._is_debug_process(cmdline) if is_ours else False
86
+ is_daemon = self._is_daemon_process(cmdline) if is_ours else False
87
+
88
+ return ProcessInfo(
89
+ pid=pid,
90
+ name=process.name(),
91
+ cmdline=cmdline,
92
+ is_ours=is_ours,
93
+ is_debug=is_debug,
94
+ is_daemon=is_daemon
95
+ )
96
+ except (psutil.NoSuchProcess, psutil.AccessDenied) as e:
97
+ # Process exists but we can't access it
98
+ return ProcessInfo(
99
+ pid=pid,
100
+ name="unknown",
101
+ cmdline="<permission denied>",
102
+ is_ours=False,
103
+ is_debug=False,
104
+ is_daemon=False
105
+ )
106
+ except (subprocess.TimeoutExpired, FileNotFoundError):
107
+ # lsof not available or timed out, fall back to psutil
108
+ pass
109
+
110
+ # Fallback to psutil method
111
+ for conn in psutil.net_connections(kind='inet'):
112
+ if conn.laddr.port == port and conn.status == 'LISTEN':
113
+ try:
114
+ process = psutil.Process(conn.pid)
115
+ cmdline = ' '.join(process.cmdline())
116
+
117
+ # Determine if this is our process and what type
118
+ is_ours = self._is_our_process(conn.pid, cmdline)
119
+ is_debug = self._is_debug_process(cmdline) if is_ours else False
120
+ is_daemon = self._is_daemon_process(cmdline) if is_ours else False
121
+
122
+ return ProcessInfo(
123
+ pid=conn.pid,
124
+ name=process.name(),
125
+ cmdline=cmdline,
126
+ is_ours=is_ours,
127
+ is_debug=is_debug,
128
+ is_daemon=is_daemon
129
+ )
130
+ except (psutil.NoSuchProcess, psutil.AccessDenied):
131
+ # Can't access process details, mark as unknown external
132
+ return ProcessInfo(
133
+ pid=conn.pid,
134
+ name="unknown",
135
+ cmdline="<permission denied>",
136
+ is_ours=False,
137
+ is_debug=False,
138
+ is_daemon=False
139
+ )
140
+ except psutil.AccessDenied:
141
+ # No permission to check network connections
142
+ # Try socket binding as last resort
143
+ if not self.is_port_available(port):
144
+ # Port is in use but we can't determine by what
145
+ return ProcessInfo(
146
+ pid=0,
147
+ name="unknown",
148
+ cmdline="<unable to determine>",
149
+ is_ours=False,
150
+ is_debug=False,
151
+ is_daemon=False
152
+ )
153
+ except Exception as e:
154
+ self.logger.debug(f"Error getting process on port {port}: {e}")
155
+
156
+ return None
157
+
158
+ def _is_our_process(self, pid: int, cmdline: str = None) -> bool:
159
+ """Check if a process belongs to claude-mpm.
160
+
161
+ WHY: We need to distinguish our processes from external ones to know
162
+ which ports we can potentially reclaim.
163
+ """
164
+ try:
165
+ if cmdline is None:
166
+ process = psutil.Process(pid)
167
+ cmdline = ' '.join(process.cmdline())
168
+
169
+ cmdline_lower = cmdline.lower()
170
+
171
+ # Check for claude-mpm related patterns
172
+ our_patterns = [
173
+ 'claude-mpm',
174
+ 'claude_mpm',
175
+ 'socketio_debug',
176
+ 'socketio_daemon',
177
+ 'socketio_server',
178
+ str(self.project_root).lower(), # Running from our project directory
179
+ 'scripts/test_', # Our test scripts
180
+ 'scripts/debug_', # Our debug scripts
181
+ 'scripts/demo_', # Our demo scripts
182
+ 'scripts/run_', # Our run scripts
183
+ 'scripts/validate_', # Our validation scripts
184
+ ]
185
+
186
+ return any(pattern in cmdline_lower for pattern in our_patterns)
187
+
188
+ except (psutil.NoSuchProcess, psutil.AccessDenied):
189
+ return False
190
+
191
+ def _is_debug_process(self, cmdline: str) -> bool:
192
+ """Check if a process is a debug/test script (safe to kill).
193
+
194
+ WHY: Debug and test scripts can be safely terminated to reclaim ports,
195
+ unlike production daemons which should be preserved.
196
+ """
197
+ cmdline_lower = cmdline.lower()
198
+
199
+ debug_patterns = [
200
+ 'socketio_debug.py',
201
+ 'run_socketio_debug.py',
202
+ 'test_',
203
+ 'debug_',
204
+ 'demo_',
205
+ 'validate_',
206
+ 'scripts/test',
207
+ 'scripts/debug',
208
+ 'scripts/demo',
209
+ 'scripts/validate',
210
+ ]
211
+
212
+ # Also check if NOT a daemon (daemons are not debug scripts)
213
+ is_not_daemon = 'daemon' not in cmdline_lower or 'debug' in cmdline_lower
214
+
215
+ return any(pattern in cmdline_lower for pattern in debug_patterns) and is_not_daemon
216
+
217
+ def _is_daemon_process(self, cmdline: str) -> bool:
218
+ """Check if a process is a daemon (should be preserved).
219
+
220
+ WHY: Daemon processes are production services that should not be
221
+ automatically killed. Users must explicitly stop them.
222
+ """
223
+ cmdline_lower = cmdline.lower()
224
+
225
+ daemon_patterns = [
226
+ 'socketio_daemon',
227
+ 'claude-mpm monitor',
228
+ 'daemon',
229
+ ]
230
+
231
+ # Exclude debug daemons
232
+ if 'debug' in cmdline_lower:
233
+ return False
234
+
235
+ return any(pattern in cmdline_lower for pattern in daemon_patterns)
236
+
237
+ def kill_process_on_port(self, port: int, force: bool = False) -> bool:
238
+ """Kill a process using a specific port if it's safe to do so.
239
+
240
+ WHY: Automatically reclaim ports from our debug scripts while preserving
241
+ daemons and avoiding external processes.
242
+
243
+ Args:
244
+ port: Port number to reclaim
245
+ force: If True, kill even daemon processes (requires explicit user action)
246
+
247
+ Returns:
248
+ True if process was killed or port is now free, False otherwise
249
+ """
250
+ process_info = self.get_process_on_port(port)
251
+
252
+ if not process_info:
253
+ self.logger.info(f"Port {port} is already free")
254
+ return True
255
+
256
+ if not process_info.is_ours:
257
+ self.logger.warning(
258
+ f"Port {port} is used by external process '{process_info.name}' "
259
+ f"(PID: {process_info.pid}). Cannot reclaim."
260
+ )
261
+ return False
262
+
263
+ if process_info.is_daemon and not force:
264
+ self.logger.warning(
265
+ f"Port {port} is used by our daemon process (PID: {process_info.pid}). "
266
+ f"Use --force flag or stop the daemon explicitly."
267
+ )
268
+ return False
269
+
270
+ if process_info.is_debug or force:
271
+ try:
272
+ self.logger.info(
273
+ f"Killing {'debug' if process_info.is_debug else 'daemon'} process "
274
+ f"{process_info.pid} on port {port}"
275
+ )
276
+
277
+ # Try graceful termination first
278
+ os.kill(process_info.pid, signal.SIGTERM)
279
+
280
+ # Wait up to 2 seconds for graceful shutdown
281
+ for _ in range(20):
282
+ time.sleep(0.1)
283
+ if not psutil.pid_exists(process_info.pid):
284
+ self.logger.info(f"Process {process_info.pid} terminated gracefully")
285
+ return True
286
+
287
+ # Force kill if still running
288
+ self.logger.warning(f"Process {process_info.pid} didn't terminate, forcing kill")
289
+ os.kill(process_info.pid, signal.SIGKILL)
290
+ time.sleep(0.5)
291
+
292
+ if not psutil.pid_exists(process_info.pid):
293
+ self.logger.info(f"Process {process_info.pid} force killed")
294
+ return True
295
+ else:
296
+ self.logger.error(f"Failed to kill process {process_info.pid}")
297
+ return False
298
+
299
+ except Exception as e:
300
+ self.logger.error(f"Error killing process {process_info.pid}: {e}")
301
+ return False
302
+
303
+ return False
304
+
305
+ def is_claude_mpm_instance(self, port: int) -> Tuple[bool, Optional[Dict]]:
306
+ """Check if a port is being used by a claude-mpm SocketIO instance."""
307
+ instances = self.load_instances()
308
+
309
+ for instance_id, instance_info in instances.items():
310
+ if instance_info.get("port") == port:
311
+ # Check if the process is still running
312
+ pid = instance_info.get("pid")
313
+ if pid and self.is_process_running(pid):
314
+ # Verify it's actually our process
315
+ if self.is_our_socketio_process(pid):
316
+ return True, instance_info
317
+ else:
318
+ # Process is dead, clean up the instance
319
+ self.remove_instance(instance_id)
320
+
321
+ return False, None
322
+
323
+ def is_process_running(self, pid: int) -> bool:
324
+ """Check if a process with given PID is running."""
325
+ try:
326
+ return psutil.pid_exists(pid)
327
+ except Exception:
328
+ return False
329
+
330
+ def is_our_socketio_process(self, pid: int) -> bool:
331
+ """Verify that a PID belongs to our SocketIO server."""
332
+ try:
333
+ process = psutil.Process(pid)
334
+ cmdline = " ".join(process.cmdline())
335
+
336
+ # Check if it's a Python process running our SocketIO daemon
337
+ return "python" in cmdline.lower() and (
338
+ "socketio_daemon" in cmdline or "claude-mpm" in cmdline
339
+ )
340
+ except (psutil.NoSuchProcess, psutil.AccessDenied):
341
+ return False
342
+
343
+ def find_available_port(
344
+ self, preferred_port: Optional[int] = None, reclaim: bool = True
345
+ ) -> Optional[int]:
346
+ """Find an available port, preferring the specified port if given.
347
+
348
+ WHY: Enhanced to intelligently reclaim ports from our debug processes
349
+ while avoiding external processes and preserving daemons.
350
+
351
+ Args:
352
+ preferred_port: Port to try first
353
+ reclaim: If True, try to reclaim ports from our debug scripts
354
+
355
+ Returns:
356
+ Available port number or None if no ports available
357
+ """
358
+ # Try preferred port first
359
+ if preferred_port and preferred_port in self.PORT_RANGE:
360
+ if self.is_port_available(preferred_port):
361
+ return preferred_port
362
+
363
+ # Port is in use - check if we can reclaim it
364
+ if reclaim:
365
+ process_info = self.get_process_on_port(preferred_port)
366
+ if process_info and process_info.is_ours and process_info.is_debug:
367
+ self.logger.info(
368
+ f"Port {preferred_port} used by our debug process, attempting to reclaim"
369
+ )
370
+ if self.kill_process_on_port(preferred_port):
371
+ time.sleep(0.5) # Brief pause for port to be released
372
+ if self.is_port_available(preferred_port):
373
+ return preferred_port
374
+ elif process_info:
375
+ if process_info.is_daemon:
376
+ self.logger.warning(
377
+ f"Port {preferred_port} used by our daemon (PID: {process_info.pid})"
378
+ )
379
+ elif not process_info.is_ours:
380
+ self.logger.warning(
381
+ f"Port {preferred_port} used by external process '{process_info.name}'"
382
+ )
383
+
384
+ # Try default port
385
+ if self.is_port_available(self.DEFAULT_PORT):
386
+ return self.DEFAULT_PORT
387
+
388
+ # Check if we can reclaim default port
389
+ if reclaim:
390
+ process_info = self.get_process_on_port(self.DEFAULT_PORT)
391
+ if process_info and process_info.is_ours and process_info.is_debug:
392
+ self.logger.info(
393
+ f"Default port {self.DEFAULT_PORT} used by our debug process, attempting to reclaim"
394
+ )
395
+ if self.kill_process_on_port(self.DEFAULT_PORT):
396
+ time.sleep(0.5)
397
+ if self.is_port_available(self.DEFAULT_PORT):
398
+ return self.DEFAULT_PORT
399
+
400
+ # Try other ports in range
401
+ for port in self.PORT_RANGE:
402
+ if port == self.DEFAULT_PORT:
403
+ continue # Already tried
404
+
405
+ if self.is_port_available(port):
406
+ return port
407
+
408
+ # Try to reclaim if it's our debug process
409
+ if reclaim:
410
+ process_info = self.get_process_on_port(port)
411
+ if process_info and process_info.is_ours and process_info.is_debug:
412
+ self.logger.info(
413
+ f"Port {port} used by our debug process, attempting to reclaim"
414
+ )
415
+ if self.kill_process_on_port(port):
416
+ time.sleep(0.5)
417
+ if self.is_port_available(port):
418
+ self.logger.info(f"Reclaimed port {port}")
419
+ return port
420
+
421
+ self.logger.error(
422
+ f"No available ports in range {self.PORT_RANGE.start}-{self.PORT_RANGE.stop-1}"
423
+ )
424
+ return None
425
+
426
+ def register_instance(self, port: int, pid: int, host: str = "localhost") -> str:
427
+ """Register a new SocketIO server instance."""
428
+ instances = self.load_instances()
429
+
430
+ instance_id = f"socketio-{port}-{int(time.time())}"
431
+ instance_info = {
432
+ "port": port,
433
+ "pid": pid,
434
+ "host": host,
435
+ "start_time": time.time(),
436
+ "project_root": str(self.project_root),
437
+ }
438
+
439
+ instances[instance_id] = instance_info
440
+ self.save_instances(instances)
441
+
442
+ self.logger.info(
443
+ f"Registered SocketIO instance {instance_id} on port {port} (PID: {pid})"
444
+ )
445
+ return instance_id
446
+
447
+ def remove_instance(self, instance_id: str) -> bool:
448
+ """Remove a SocketIO server instance registration."""
449
+ instances = self.load_instances()
450
+
451
+ if instance_id in instances:
452
+ instance_info = instances.pop(instance_id)
453
+ self.save_instances(instances)
454
+ self.logger.info(
455
+ f"Removed SocketIO instance {instance_id} (port: {instance_info.get('port')})"
456
+ )
457
+ return True
458
+
459
+ return False
460
+
461
+ def load_instances(self) -> Dict:
462
+ """Load registered instances from file."""
463
+ try:
464
+ if self.instances_file.exists():
465
+ with open(self.instances_file, "r") as f:
466
+ return json.load(f)
467
+ except Exception as e:
468
+ self.logger.warning(f"Failed to load instances file: {e}")
469
+
470
+ return {}
471
+
472
+ def save_instances(self, instances: Dict) -> None:
473
+ """Save registered instances to file."""
474
+ try:
475
+ with open(self.instances_file, "w") as f:
476
+ json.dump(instances, f, indent=2)
477
+ except Exception as e:
478
+ self.logger.error(f"Failed to save instances file: {e}")
479
+
480
+ def cleanup_dead_instances(self) -> int:
481
+ """Clean up instances for processes that are no longer running."""
482
+ instances = self.load_instances()
483
+ dead_instances = []
484
+
485
+ for instance_id, instance_info in instances.items():
486
+ pid = instance_info.get("pid")
487
+ if pid and not self.is_process_running(pid):
488
+ dead_instances.append(instance_id)
489
+
490
+ for instance_id in dead_instances:
491
+ self.remove_instance(instance_id)
492
+
493
+ if dead_instances:
494
+ self.logger.info(f"Cleaned up {len(dead_instances)} dead instances")
495
+
496
+ return len(dead_instances)
497
+
498
+ def list_active_instances(self) -> List[Dict]:
499
+ """List all active SocketIO instances."""
500
+ instances = self.load_instances()
501
+ active_instances = []
502
+
503
+ for instance_id, instance_info in instances.items():
504
+ pid = instance_info.get("pid")
505
+ if pid and self.is_process_running(pid):
506
+ instance_info["instance_id"] = instance_id
507
+ instance_info["running"] = True
508
+ active_instances.append(instance_info)
509
+
510
+ return active_instances
511
+
512
+ def get_instance_by_port(self, port: int) -> Optional[Dict]:
513
+ """Get instance information for a specific port."""
514
+ instances = self.load_instances()
515
+
516
+ for instance_id, instance_info in instances.items():
517
+ if instance_info.get("port") == port:
518
+ pid = instance_info.get("pid")
519
+ if pid and self.is_process_running(pid):
520
+ instance_info["instance_id"] = instance_id
521
+ instance_info["running"] = True
522
+ return instance_info
523
+
524
+ return None
525
+
526
+ def get_port_status(self, port: int) -> Dict[str, any]:
527
+ """Get detailed status of a port including what's using it.
528
+
529
+ WHY: Provides comprehensive information for users to understand
530
+ port conflicts and make informed decisions.
531
+
532
+ Returns:
533
+ Dictionary with port status details
534
+ """
535
+ status = {
536
+ "port": port,
537
+ "available": self.is_port_available(port),
538
+ "process": None,
539
+ "instance": None,
540
+ "recommendation": None
541
+ }
542
+
543
+ # Check for process using the port
544
+ process_info = self.get_process_on_port(port)
545
+ if process_info:
546
+ status["process"] = {
547
+ "pid": process_info.pid,
548
+ "name": process_info.name,
549
+ "is_ours": process_info.is_ours,
550
+ "is_debug": process_info.is_debug,
551
+ "is_daemon": process_info.is_daemon,
552
+ "cmdline": process_info.cmdline[:100] + "..." if len(process_info.cmdline) > 100 else process_info.cmdline
553
+ }
554
+
555
+ # Provide recommendation based on process type
556
+ if process_info.is_ours:
557
+ if process_info.is_debug:
558
+ status["recommendation"] = "Can be automatically reclaimed (debug process)"
559
+ elif process_info.is_daemon:
560
+ status["recommendation"] = "Stop daemon with 'claude-mpm monitor stop' or use --force"
561
+ else:
562
+ status["recommendation"] = "Our process, consider stopping it manually"
563
+ else:
564
+ status["recommendation"] = "External process, choose a different port"
565
+
566
+ # Check for registered instance
567
+ instance_info = self.get_instance_by_port(port)
568
+ if instance_info:
569
+ status["instance"] = {
570
+ "id": instance_info.get("instance_id"),
571
+ "pid": instance_info.get("pid"),
572
+ "start_time": instance_info.get("start_time")
573
+ }
574
+
575
+ return status
@@ -16,6 +16,6 @@ from .analyzer import ProjectAnalyzer
16
16
  from .registry import ProjectRegistry
17
17
 
18
18
  __all__ = [
19
- 'ProjectAnalyzer',
20
- 'ProjectRegistry',
21
- ]
19
+ "ProjectAnalyzer",
20
+ "ProjectRegistry",
21
+ ]