claude-mpm 4.1.1__py3-none-any.whl → 4.1.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.
Files changed (389) hide show
  1. claude_mpm/BUILD_NUMBER +1 -1
  2. claude_mpm/VERSION +1 -1
  3. claude_mpm/__main__.py +1 -1
  4. claude_mpm/agents/BASE_PM.md +74 -46
  5. claude_mpm/agents/INSTRUCTIONS.md +11 -153
  6. claude_mpm/agents/WORKFLOW.md +61 -321
  7. claude_mpm/agents/__init__.py +11 -11
  8. claude_mpm/agents/agent_loader.py +23 -20
  9. claude_mpm/agents/agent_loader_integration.py +1 -1
  10. claude_mpm/agents/agents_metadata.py +27 -0
  11. claude_mpm/agents/async_agent_loader.py +5 -8
  12. claude_mpm/agents/base_agent_loader.py +36 -25
  13. claude_mpm/agents/frontmatter_validator.py +6 -6
  14. claude_mpm/agents/schema/agent_schema.json +1 -1
  15. claude_mpm/agents/system_agent_config.py +9 -9
  16. claude_mpm/agents/templates/api_qa.json +47 -2
  17. claude_mpm/agents/templates/engineer.json +33 -11
  18. claude_mpm/agents/templates/imagemagick.json +256 -0
  19. claude_mpm/agents/templates/qa.json +41 -2
  20. claude_mpm/agents/templates/ticketing.json +5 -5
  21. claude_mpm/agents/templates/web_qa.json +50 -2
  22. claude_mpm/cli/__init__.py +51 -46
  23. claude_mpm/cli/__main__.py +1 -1
  24. claude_mpm/cli/commands/__init__.py +10 -12
  25. claude_mpm/cli/commands/agent_manager.py +186 -181
  26. claude_mpm/cli/commands/agents.py +648 -1098
  27. claude_mpm/cli/commands/aggregate.py +30 -29
  28. claude_mpm/cli/commands/cleanup.py +50 -44
  29. claude_mpm/cli/commands/cleanup_orphaned_agents.py +25 -25
  30. claude_mpm/cli/commands/config.py +162 -127
  31. claude_mpm/cli/commands/doctor.py +52 -62
  32. claude_mpm/cli/commands/info.py +37 -25
  33. claude_mpm/cli/commands/mcp.py +3 -7
  34. claude_mpm/cli/commands/mcp_command_router.py +14 -18
  35. claude_mpm/cli/commands/mcp_install_commands.py +28 -23
  36. claude_mpm/cli/commands/mcp_pipx_config.py +58 -49
  37. claude_mpm/cli/commands/mcp_server_commands.py +23 -17
  38. claude_mpm/cli/commands/memory.py +339 -967
  39. claude_mpm/cli/commands/monitor.py +117 -88
  40. claude_mpm/cli/commands/run.py +233 -542
  41. claude_mpm/cli/commands/socketio_monitor.py +17 -19
  42. claude_mpm/cli/commands/tickets.py +92 -92
  43. claude_mpm/cli/parser.py +1 -5
  44. claude_mpm/cli/parsers/__init__.py +1 -1
  45. claude_mpm/cli/parsers/agent_manager_parser.py +50 -98
  46. claude_mpm/cli/parsers/agents_parser.py +2 -3
  47. claude_mpm/cli/parsers/base_parser.py +7 -5
  48. claude_mpm/cli/parsers/mcp_parser.py +4 -2
  49. claude_mpm/cli/parsers/monitor_parser.py +26 -18
  50. claude_mpm/cli/shared/__init__.py +10 -10
  51. claude_mpm/cli/shared/argument_patterns.py +57 -71
  52. claude_mpm/cli/shared/base_command.py +61 -53
  53. claude_mpm/cli/shared/error_handling.py +62 -58
  54. claude_mpm/cli/shared/output_formatters.py +78 -77
  55. claude_mpm/cli/startup_logging.py +280 -172
  56. claude_mpm/cli/utils.py +10 -11
  57. claude_mpm/cli_module/__init__.py +1 -1
  58. claude_mpm/cli_module/args.py +1 -1
  59. claude_mpm/cli_module/migration_example.py +5 -5
  60. claude_mpm/config/__init__.py +9 -9
  61. claude_mpm/config/agent_config.py +15 -14
  62. claude_mpm/config/experimental_features.py +4 -4
  63. claude_mpm/config/paths.py +0 -1
  64. claude_mpm/config/socketio_config.py +5 -6
  65. claude_mpm/constants.py +1 -2
  66. claude_mpm/core/__init__.py +8 -8
  67. claude_mpm/core/agent_name_normalizer.py +1 -1
  68. claude_mpm/core/agent_registry.py +22 -29
  69. claude_mpm/core/agent_session_manager.py +3 -3
  70. claude_mpm/core/base_service.py +7 -15
  71. claude_mpm/core/cache.py +4 -6
  72. claude_mpm/core/claude_runner.py +85 -113
  73. claude_mpm/core/config.py +43 -28
  74. claude_mpm/core/config_aliases.py +0 -9
  75. claude_mpm/core/config_constants.py +52 -30
  76. claude_mpm/core/constants.py +0 -1
  77. claude_mpm/core/container.py +18 -27
  78. claude_mpm/core/exceptions.py +2 -2
  79. claude_mpm/core/factories.py +10 -12
  80. claude_mpm/core/framework_loader.py +500 -680
  81. claude_mpm/core/hook_manager.py +26 -22
  82. claude_mpm/core/hook_performance_config.py +58 -47
  83. claude_mpm/core/injectable_service.py +1 -1
  84. claude_mpm/core/interactive_session.py +61 -152
  85. claude_mpm/core/interfaces.py +1 -100
  86. claude_mpm/core/lazy.py +5 -5
  87. claude_mpm/core/log_manager.py +587 -0
  88. claude_mpm/core/logger.py +125 -8
  89. claude_mpm/core/logging_config.py +15 -17
  90. claude_mpm/core/minimal_framework_loader.py +5 -8
  91. claude_mpm/core/oneshot_session.py +15 -33
  92. claude_mpm/core/optimized_agent_loader.py +4 -6
  93. claude_mpm/core/optimized_startup.py +2 -1
  94. claude_mpm/core/output_style_manager.py +147 -106
  95. claude_mpm/core/pm_hook_interceptor.py +0 -1
  96. claude_mpm/core/service_registry.py +11 -8
  97. claude_mpm/core/session_manager.py +1 -2
  98. claude_mpm/core/shared/__init__.py +1 -1
  99. claude_mpm/core/shared/config_loader.py +101 -97
  100. claude_mpm/core/shared/path_resolver.py +72 -68
  101. claude_mpm/core/shared/singleton_manager.py +56 -50
  102. claude_mpm/core/socketio_pool.py +26 -6
  103. claude_mpm/core/tool_access_control.py +4 -5
  104. claude_mpm/core/typing_utils.py +50 -59
  105. claude_mpm/core/unified_agent_registry.py +14 -19
  106. claude_mpm/core/unified_config.py +4 -6
  107. claude_mpm/core/unified_paths.py +197 -109
  108. claude_mpm/dashboard/open_dashboard.py +2 -4
  109. claude_mpm/experimental/cli_enhancements.py +51 -36
  110. claude_mpm/generators/agent_profile_generator.py +2 -4
  111. claude_mpm/hooks/base_hook.py +1 -2
  112. claude_mpm/hooks/claude_hooks/connection_pool.py +72 -26
  113. claude_mpm/hooks/claude_hooks/event_handlers.py +99 -154
  114. claude_mpm/hooks/claude_hooks/hook_handler.py +110 -720
  115. claude_mpm/hooks/claude_hooks/hook_handler_eventbus.py +104 -77
  116. claude_mpm/hooks/claude_hooks/hook_handler_original.py +1040 -0
  117. claude_mpm/hooks/claude_hooks/hook_handler_refactored.py +347 -0
  118. claude_mpm/hooks/claude_hooks/memory_integration.py +2 -4
  119. claude_mpm/hooks/claude_hooks/response_tracking.py +15 -11
  120. claude_mpm/hooks/claude_hooks/services/__init__.py +13 -0
  121. claude_mpm/hooks/claude_hooks/services/connection_manager.py +190 -0
  122. claude_mpm/hooks/claude_hooks/services/duplicate_detector.py +106 -0
  123. claude_mpm/hooks/claude_hooks/services/state_manager.py +282 -0
  124. claude_mpm/hooks/claude_hooks/services/subagent_processor.py +374 -0
  125. claude_mpm/hooks/claude_hooks/tool_analysis.py +12 -18
  126. claude_mpm/hooks/memory_integration_hook.py +5 -5
  127. claude_mpm/hooks/tool_call_interceptor.py +1 -1
  128. claude_mpm/hooks/validation_hooks.py +4 -4
  129. claude_mpm/init.py +4 -9
  130. claude_mpm/models/__init__.py +2 -2
  131. claude_mpm/models/agent_session.py +11 -14
  132. claude_mpm/scripts/mcp_server.py +20 -11
  133. claude_mpm/scripts/mcp_wrapper.py +5 -5
  134. claude_mpm/scripts/mpm_doctor.py +321 -0
  135. claude_mpm/scripts/socketio_daemon.py +28 -25
  136. claude_mpm/scripts/socketio_daemon_hardened.py +298 -258
  137. claude_mpm/scripts/socketio_server_manager.py +116 -95
  138. claude_mpm/services/__init__.py +49 -49
  139. claude_mpm/services/agent_capabilities_service.py +12 -18
  140. claude_mpm/services/agents/__init__.py +22 -22
  141. claude_mpm/services/agents/agent_builder.py +140 -119
  142. claude_mpm/services/agents/deployment/__init__.py +3 -3
  143. claude_mpm/services/agents/deployment/agent_config_provider.py +9 -9
  144. claude_mpm/services/agents/deployment/agent_configuration_manager.py +19 -20
  145. claude_mpm/services/agents/deployment/agent_definition_factory.py +1 -5
  146. claude_mpm/services/agents/deployment/agent_deployment.py +129 -511
  147. claude_mpm/services/agents/deployment/agent_discovery_service.py +4 -8
  148. claude_mpm/services/agents/deployment/agent_environment_manager.py +2 -7
  149. claude_mpm/services/agents/deployment/agent_filesystem_manager.py +6 -10
  150. claude_mpm/services/agents/deployment/agent_format_converter.py +11 -15
  151. claude_mpm/services/agents/deployment/agent_frontmatter_validator.py +2 -3
  152. claude_mpm/services/agents/deployment/agent_lifecycle_manager.py +5 -5
  153. claude_mpm/services/agents/deployment/agent_metrics_collector.py +13 -19
  154. claude_mpm/services/agents/deployment/agent_restore_handler.py +0 -1
  155. claude_mpm/services/agents/deployment/agent_template_builder.py +26 -35
  156. claude_mpm/services/agents/deployment/agent_validator.py +0 -1
  157. claude_mpm/services/agents/deployment/agent_version_manager.py +7 -9
  158. claude_mpm/services/agents/deployment/agent_versioning.py +3 -3
  159. claude_mpm/services/agents/deployment/agents_directory_resolver.py +6 -7
  160. claude_mpm/services/agents/deployment/async_agent_deployment.py +51 -38
  161. claude_mpm/services/agents/deployment/base_agent_locator.py +132 -0
  162. claude_mpm/services/agents/deployment/config/__init__.py +1 -1
  163. claude_mpm/services/agents/deployment/config/deployment_config.py +7 -8
  164. claude_mpm/services/agents/deployment/deployment_results_manager.py +185 -0
  165. claude_mpm/services/agents/deployment/deployment_type_detector.py +1 -1
  166. claude_mpm/services/agents/deployment/deployment_wrapper.py +18 -18
  167. claude_mpm/services/agents/deployment/facade/__init__.py +1 -1
  168. claude_mpm/services/agents/deployment/facade/deployment_executor.py +0 -3
  169. claude_mpm/services/agents/deployment/facade/deployment_facade.py +3 -4
  170. claude_mpm/services/agents/deployment/interface_adapter.py +5 -7
  171. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +345 -276
  172. claude_mpm/services/agents/deployment/pipeline/__init__.py +2 -2
  173. claude_mpm/services/agents/deployment/pipeline/pipeline_builder.py +1 -1
  174. claude_mpm/services/agents/deployment/pipeline/pipeline_context.py +6 -4
  175. claude_mpm/services/agents/deployment/pipeline/pipeline_executor.py +3 -3
  176. claude_mpm/services/agents/deployment/pipeline/steps/__init__.py +2 -2
  177. claude_mpm/services/agents/deployment/pipeline/steps/agent_processing_step.py +14 -13
  178. claude_mpm/services/agents/deployment/pipeline/steps/base_step.py +0 -1
  179. claude_mpm/services/agents/deployment/pipeline/steps/configuration_step.py +1 -1
  180. claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +8 -9
  181. claude_mpm/services/agents/deployment/pipeline/steps/validation_step.py +1 -1
  182. claude_mpm/services/agents/deployment/processors/__init__.py +1 -1
  183. claude_mpm/services/agents/deployment/processors/agent_processor.py +20 -16
  184. claude_mpm/services/agents/deployment/refactored_agent_deployment_service.py +5 -12
  185. claude_mpm/services/agents/deployment/results/__init__.py +1 -1
  186. claude_mpm/services/agents/deployment/results/deployment_result_builder.py +1 -1
  187. claude_mpm/services/agents/deployment/single_agent_deployer.py +315 -0
  188. claude_mpm/services/agents/deployment/strategies/__init__.py +2 -2
  189. claude_mpm/services/agents/deployment/strategies/base_strategy.py +1 -7
  190. claude_mpm/services/agents/deployment/strategies/project_strategy.py +1 -4
  191. claude_mpm/services/agents/deployment/strategies/system_strategy.py +2 -3
  192. claude_mpm/services/agents/deployment/strategies/user_strategy.py +3 -7
  193. claude_mpm/services/agents/deployment/validation/__init__.py +1 -1
  194. claude_mpm/services/agents/deployment/validation/agent_validator.py +1 -1
  195. claude_mpm/services/agents/deployment/validation/template_validator.py +2 -2
  196. claude_mpm/services/agents/deployment/validation/validation_result.py +2 -6
  197. claude_mpm/services/agents/loading/__init__.py +1 -1
  198. claude_mpm/services/agents/loading/agent_profile_loader.py +6 -12
  199. claude_mpm/services/agents/loading/base_agent_manager.py +5 -5
  200. claude_mpm/services/agents/loading/framework_agent_loader.py +2 -4
  201. claude_mpm/services/agents/management/__init__.py +1 -1
  202. claude_mpm/services/agents/management/agent_capabilities_generator.py +1 -3
  203. claude_mpm/services/agents/management/agent_management_service.py +5 -9
  204. claude_mpm/services/agents/memory/__init__.py +4 -4
  205. claude_mpm/services/agents/memory/agent_memory_manager.py +157 -503
  206. claude_mpm/services/agents/memory/agent_persistence_service.py +0 -2
  207. claude_mpm/services/agents/memory/content_manager.py +44 -38
  208. claude_mpm/services/agents/memory/memory_categorization_service.py +165 -0
  209. claude_mpm/services/agents/memory/memory_file_service.py +103 -0
  210. claude_mpm/services/agents/memory/memory_format_service.py +201 -0
  211. claude_mpm/services/agents/memory/memory_limits_service.py +99 -0
  212. claude_mpm/services/agents/memory/template_generator.py +4 -6
  213. claude_mpm/services/agents/registry/__init__.py +11 -7
  214. claude_mpm/services/agents/registry/deployed_agent_discovery.py +30 -27
  215. claude_mpm/services/agents/registry/modification_tracker.py +3 -6
  216. claude_mpm/services/async_session_logger.py +1 -2
  217. claude_mpm/services/claude_session_logger.py +1 -2
  218. claude_mpm/services/cli/__init__.py +18 -0
  219. claude_mpm/services/cli/agent_cleanup_service.py +407 -0
  220. claude_mpm/services/cli/agent_dependency_service.py +395 -0
  221. claude_mpm/services/cli/agent_listing_service.py +463 -0
  222. claude_mpm/services/cli/agent_output_formatter.py +605 -0
  223. claude_mpm/services/cli/agent_validation_service.py +589 -0
  224. claude_mpm/services/cli/dashboard_launcher.py +424 -0
  225. claude_mpm/services/cli/memory_crud_service.py +617 -0
  226. claude_mpm/services/cli/memory_output_formatter.py +604 -0
  227. claude_mpm/services/cli/session_manager.py +513 -0
  228. claude_mpm/services/cli/socketio_manager.py +498 -0
  229. claude_mpm/services/cli/startup_checker.py +370 -0
  230. claude_mpm/services/command_deployment_service.py +173 -0
  231. claude_mpm/services/command_handler_service.py +20 -22
  232. claude_mpm/services/core/__init__.py +25 -25
  233. claude_mpm/services/core/base.py +0 -5
  234. claude_mpm/services/core/cache_manager.py +311 -0
  235. claude_mpm/services/core/interfaces/__init__.py +32 -32
  236. claude_mpm/services/core/interfaces/agent.py +0 -21
  237. claude_mpm/services/core/interfaces/communication.py +0 -27
  238. claude_mpm/services/core/interfaces/infrastructure.py +0 -56
  239. claude_mpm/services/core/interfaces/service.py +0 -29
  240. claude_mpm/services/core/memory_manager.py +637 -0
  241. claude_mpm/services/core/path_resolver.py +498 -0
  242. claude_mpm/services/core/service_container.py +520 -0
  243. claude_mpm/services/core/service_interfaces.py +436 -0
  244. claude_mpm/services/diagnostics/__init__.py +1 -1
  245. claude_mpm/services/diagnostics/checks/__init__.py +6 -6
  246. claude_mpm/services/diagnostics/checks/agent_check.py +152 -97
  247. claude_mpm/services/diagnostics/checks/base_check.py +12 -16
  248. claude_mpm/services/diagnostics/checks/claude_desktop_check.py +84 -81
  249. claude_mpm/services/diagnostics/checks/common_issues_check.py +99 -91
  250. claude_mpm/services/diagnostics/checks/configuration_check.py +82 -77
  251. claude_mpm/services/diagnostics/checks/filesystem_check.py +67 -68
  252. claude_mpm/services/diagnostics/checks/installation_check.py +254 -94
  253. claude_mpm/services/diagnostics/checks/mcp_check.py +90 -88
  254. claude_mpm/services/diagnostics/checks/monitor_check.py +75 -76
  255. claude_mpm/services/diagnostics/checks/startup_log_check.py +67 -73
  256. claude_mpm/services/diagnostics/diagnostic_runner.py +67 -59
  257. claude_mpm/services/diagnostics/doctor_reporter.py +107 -70
  258. claude_mpm/services/diagnostics/models.py +21 -19
  259. claude_mpm/services/event_aggregator.py +10 -17
  260. claude_mpm/services/event_bus/__init__.py +1 -1
  261. claude_mpm/services/event_bus/config.py +54 -35
  262. claude_mpm/services/event_bus/event_bus.py +76 -71
  263. claude_mpm/services/event_bus/relay.py +74 -64
  264. claude_mpm/services/events/__init__.py +11 -11
  265. claude_mpm/services/events/consumers/__init__.py +3 -3
  266. claude_mpm/services/events/consumers/dead_letter.py +71 -63
  267. claude_mpm/services/events/consumers/logging.py +39 -37
  268. claude_mpm/services/events/consumers/metrics.py +56 -57
  269. claude_mpm/services/events/consumers/socketio.py +82 -81
  270. claude_mpm/services/events/core.py +110 -99
  271. claude_mpm/services/events/interfaces.py +56 -72
  272. claude_mpm/services/events/producers/__init__.py +1 -1
  273. claude_mpm/services/events/producers/hook.py +38 -38
  274. claude_mpm/services/events/producers/system.py +46 -44
  275. claude_mpm/services/exceptions.py +81 -80
  276. claude_mpm/services/framework_claude_md_generator/__init__.py +2 -4
  277. claude_mpm/services/framework_claude_md_generator/content_assembler.py +3 -5
  278. claude_mpm/services/framework_claude_md_generator/content_validator.py +1 -1
  279. claude_mpm/services/framework_claude_md_generator/deployment_manager.py +4 -4
  280. claude_mpm/services/framework_claude_md_generator/section_generators/__init__.py +0 -1
  281. claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +0 -2
  282. claude_mpm/services/framework_claude_md_generator/version_manager.py +4 -5
  283. claude_mpm/services/hook_service.py +6 -9
  284. claude_mpm/services/infrastructure/__init__.py +1 -1
  285. claude_mpm/services/infrastructure/context_preservation.py +8 -12
  286. claude_mpm/services/infrastructure/monitoring.py +21 -23
  287. claude_mpm/services/mcp_gateway/__init__.py +37 -37
  288. claude_mpm/services/mcp_gateway/auto_configure.py +95 -103
  289. claude_mpm/services/mcp_gateway/config/__init__.py +1 -1
  290. claude_mpm/services/mcp_gateway/config/config_loader.py +23 -25
  291. claude_mpm/services/mcp_gateway/config/config_schema.py +5 -5
  292. claude_mpm/services/mcp_gateway/config/configuration.py +9 -6
  293. claude_mpm/services/mcp_gateway/core/__init__.py +10 -10
  294. claude_mpm/services/mcp_gateway/core/base.py +0 -3
  295. claude_mpm/services/mcp_gateway/core/interfaces.py +1 -38
  296. claude_mpm/services/mcp_gateway/core/process_pool.py +99 -93
  297. claude_mpm/services/mcp_gateway/core/singleton_manager.py +65 -62
  298. claude_mpm/services/mcp_gateway/core/startup_verification.py +75 -74
  299. claude_mpm/services/mcp_gateway/main.py +2 -1
  300. claude_mpm/services/mcp_gateway/registry/service_registry.py +5 -8
  301. claude_mpm/services/mcp_gateway/registry/tool_registry.py +1 -1
  302. claude_mpm/services/mcp_gateway/server/__init__.py +1 -1
  303. claude_mpm/services/mcp_gateway/server/mcp_gateway.py +12 -19
  304. claude_mpm/services/mcp_gateway/server/stdio_handler.py +4 -3
  305. claude_mpm/services/mcp_gateway/server/stdio_server.py +79 -71
  306. claude_mpm/services/mcp_gateway/tools/__init__.py +2 -2
  307. claude_mpm/services/mcp_gateway/tools/base_adapter.py +5 -6
  308. claude_mpm/services/mcp_gateway/tools/document_summarizer.py +13 -22
  309. claude_mpm/services/mcp_gateway/tools/health_check_tool.py +79 -78
  310. claude_mpm/services/mcp_gateway/tools/hello_world.py +12 -14
  311. claude_mpm/services/mcp_gateway/tools/ticket_tools.py +42 -49
  312. claude_mpm/services/mcp_gateway/tools/unified_ticket_tool.py +51 -55
  313. claude_mpm/services/memory/__init__.py +3 -3
  314. claude_mpm/services/memory/builder.py +3 -6
  315. claude_mpm/services/memory/cache/__init__.py +1 -1
  316. claude_mpm/services/memory/cache/shared_prompt_cache.py +3 -5
  317. claude_mpm/services/memory/cache/simple_cache.py +1 -1
  318. claude_mpm/services/memory/indexed_memory.py +5 -7
  319. claude_mpm/services/memory/optimizer.py +7 -10
  320. claude_mpm/services/memory/router.py +8 -9
  321. claude_mpm/services/memory_hook_service.py +48 -34
  322. claude_mpm/services/monitor_build_service.py +77 -73
  323. claude_mpm/services/port_manager.py +130 -108
  324. claude_mpm/services/project/analyzer.py +12 -10
  325. claude_mpm/services/project/registry.py +11 -11
  326. claude_mpm/services/recovery_manager.py +10 -19
  327. claude_mpm/services/response_tracker.py +0 -1
  328. claude_mpm/services/runner_configuration_service.py +19 -20
  329. claude_mpm/services/session_management_service.py +7 -11
  330. claude_mpm/services/shared/__init__.py +1 -1
  331. claude_mpm/services/shared/async_service_base.py +58 -50
  332. claude_mpm/services/shared/config_service_base.py +73 -67
  333. claude_mpm/services/shared/lifecycle_service_base.py +82 -78
  334. claude_mpm/services/shared/manager_base.py +94 -82
  335. claude_mpm/services/shared/service_factory.py +96 -98
  336. claude_mpm/services/socketio/__init__.py +3 -3
  337. claude_mpm/services/socketio/client_proxy.py +5 -5
  338. claude_mpm/services/socketio/event_normalizer.py +199 -181
  339. claude_mpm/services/socketio/handlers/__init__.py +3 -3
  340. claude_mpm/services/socketio/handlers/base.py +5 -4
  341. claude_mpm/services/socketio/handlers/connection.py +163 -136
  342. claude_mpm/services/socketio/handlers/file.py +13 -14
  343. claude_mpm/services/socketio/handlers/git.py +12 -7
  344. claude_mpm/services/socketio/handlers/hook.py +49 -44
  345. claude_mpm/services/socketio/handlers/memory.py +0 -1
  346. claude_mpm/services/socketio/handlers/project.py +0 -1
  347. claude_mpm/services/socketio/handlers/registry.py +37 -19
  348. claude_mpm/services/socketio/migration_utils.py +98 -84
  349. claude_mpm/services/socketio/server/__init__.py +1 -1
  350. claude_mpm/services/socketio/server/broadcaster.py +81 -87
  351. claude_mpm/services/socketio/server/core.py +65 -54
  352. claude_mpm/services/socketio/server/eventbus_integration.py +95 -56
  353. claude_mpm/services/socketio/server/main.py +64 -38
  354. claude_mpm/services/socketio_client_manager.py +10 -12
  355. claude_mpm/services/subprocess_launcher_service.py +4 -7
  356. claude_mpm/services/system_instructions_service.py +13 -14
  357. claude_mpm/services/ticket_manager.py +2 -2
  358. claude_mpm/services/utility_service.py +5 -13
  359. claude_mpm/services/version_control/__init__.py +16 -16
  360. claude_mpm/services/version_control/branch_strategy.py +5 -8
  361. claude_mpm/services/version_control/conflict_resolution.py +9 -23
  362. claude_mpm/services/version_control/git_operations.py +5 -7
  363. claude_mpm/services/version_control/semantic_versioning.py +16 -17
  364. claude_mpm/services/version_control/version_parser.py +13 -18
  365. claude_mpm/services/version_service.py +10 -11
  366. claude_mpm/storage/__init__.py +1 -1
  367. claude_mpm/storage/state_storage.py +22 -28
  368. claude_mpm/utils/__init__.py +6 -6
  369. claude_mpm/utils/agent_dependency_loader.py +47 -33
  370. claude_mpm/utils/config_manager.py +11 -14
  371. claude_mpm/utils/dependency_cache.py +1 -1
  372. claude_mpm/utils/dependency_manager.py +13 -17
  373. claude_mpm/utils/dependency_strategies.py +8 -10
  374. claude_mpm/utils/environment_context.py +3 -9
  375. claude_mpm/utils/error_handler.py +3 -13
  376. claude_mpm/utils/file_utils.py +1 -1
  377. claude_mpm/utils/path_operations.py +8 -12
  378. claude_mpm/utils/robust_installer.py +110 -33
  379. claude_mpm/utils/subprocess_utils.py +5 -6
  380. claude_mpm/validation/agent_validator.py +3 -6
  381. claude_mpm/validation/frontmatter_validator.py +1 -1
  382. {claude_mpm-4.1.1.dist-info → claude_mpm-4.1.3.dist-info}/METADATA +1 -1
  383. claude_mpm-4.1.3.dist-info/RECORD +528 -0
  384. claude_mpm/cli/commands/run_config_checker.py +0 -160
  385. claude_mpm-4.1.1.dist-info/RECORD +0 -494
  386. {claude_mpm-4.1.1.dist-info → claude_mpm-4.1.3.dist-info}/WHEEL +0 -0
  387. {claude_mpm-4.1.1.dist-info → claude_mpm-4.1.3.dist-info}/entry_points.txt +0 -0
  388. {claude_mpm-4.1.1.dist-info → claude_mpm-4.1.3.dist-info}/licenses/LICENSE +0 -0
  389. {claude_mpm-4.1.1.dist-info → claude_mpm-4.1.3.dist-info}/top_level.txt +0 -0
@@ -12,7 +12,6 @@ Key Features:
12
12
  """
13
13
 
14
14
  import json
15
- import logging
16
15
  import os
17
16
  from pathlib import Path
18
17
  from typing import Any, Dict, List, Optional, Tuple
@@ -26,112 +25,114 @@ from .agent_version_manager import AgentVersionManager
26
25
 
27
26
  class MultiSourceAgentDeploymentService:
28
27
  """Service for deploying agents from multiple sources with version comparison.
29
-
28
+
30
29
  This service ensures that the highest version of each agent is deployed,
31
30
  regardless of whether it comes from system templates, project agents, or
32
31
  user agents.
33
-
32
+
34
33
  WHY: The current system processes agents from a single source at a time,
35
34
  which can result in lower version agents being deployed if they exist in
36
35
  a higher priority source. This service fixes that by comparing versions
37
36
  across all sources.
38
37
  """
39
-
38
+
40
39
  def __init__(self):
41
40
  """Initialize the multi-source deployment service."""
42
41
  self.logger = get_logger(__name__)
43
42
  self.version_manager = AgentVersionManager()
44
-
43
+
45
44
  def discover_agents_from_all_sources(
46
- self,
45
+ self,
47
46
  system_templates_dir: Optional[Path] = None,
48
47
  project_agents_dir: Optional[Path] = None,
49
48
  user_agents_dir: Optional[Path] = None,
50
- working_directory: Optional[Path] = None
49
+ working_directory: Optional[Path] = None,
51
50
  ) -> Dict[str, List[Dict[str, Any]]]:
52
51
  """Discover agents from all available sources.
53
-
52
+
54
53
  Args:
55
54
  system_templates_dir: Directory containing system agent templates
56
55
  project_agents_dir: Directory containing project-specific agents
57
56
  user_agents_dir: Directory containing user custom agents
58
57
  working_directory: Current working directory for finding project agents
59
-
58
+
60
59
  Returns:
61
60
  Dictionary mapping agent names to list of agent info from different sources
62
61
  """
63
62
  agents_by_name = {}
64
-
63
+
65
64
  # Determine directories if not provided
66
65
  if not system_templates_dir:
67
66
  # Use default system templates location
68
67
  from claude_mpm.config.paths import paths
68
+
69
69
  system_templates_dir = paths.agents_dir / "templates"
70
-
70
+
71
71
  if not project_agents_dir and working_directory:
72
72
  # Check for project agents in working directory
73
73
  project_agents_dir = working_directory / ".claude-mpm" / "agents"
74
74
  if not project_agents_dir.exists():
75
75
  project_agents_dir = None
76
-
76
+
77
77
  if not user_agents_dir:
78
78
  # Check for user agents in home directory
79
79
  user_agents_dir = Path.home() / ".claude-mpm" / "agents"
80
80
  if not user_agents_dir.exists():
81
81
  user_agents_dir = None
82
-
82
+
83
83
  # Discover agents from each source
84
84
  sources = [
85
85
  ("system", system_templates_dir),
86
86
  ("project", project_agents_dir),
87
- ("user", user_agents_dir)
87
+ ("user", user_agents_dir),
88
88
  ]
89
-
89
+
90
90
  for source_name, source_dir in sources:
91
91
  if source_dir and source_dir.exists():
92
- self.logger.debug(f"Discovering agents from {source_name} source: {source_dir}")
92
+ self.logger.debug(
93
+ f"Discovering agents from {source_name} source: {source_dir}"
94
+ )
93
95
  discovery_service = AgentDiscoveryService(source_dir)
94
96
  agents = discovery_service.list_available_agents()
95
-
97
+
96
98
  for agent_info in agents:
97
99
  agent_name = agent_info.get("name")
98
100
  if not agent_name:
99
101
  continue
100
-
102
+
101
103
  # Add source information
102
104
  agent_info["source"] = source_name
103
105
  agent_info["source_dir"] = str(source_dir)
104
-
106
+
105
107
  # Initialize list if this is the first occurrence of this agent
106
108
  if agent_name not in agents_by_name:
107
109
  agents_by_name[agent_name] = []
108
-
110
+
109
111
  agents_by_name[agent_name].append(agent_info)
110
-
112
+
111
113
  self.logger.info(
112
114
  f"Discovered {len(agents)} agents from {source_name} source"
113
115
  )
114
-
116
+
115
117
  return agents_by_name
116
-
118
+
117
119
  def select_highest_version_agents(
118
- self,
119
- agents_by_name: Dict[str, List[Dict[str, Any]]]
120
+ self, agents_by_name: Dict[str, List[Dict[str, Any]]]
120
121
  ) -> Dict[str, Dict[str, Any]]:
121
122
  """Select the highest version agent from multiple sources.
122
-
123
+
123
124
  Args:
124
125
  agents_by_name: Dictionary mapping agent names to list of agent info
125
-
126
+
126
127
  Returns:
127
128
  Dictionary mapping agent names to the highest version agent info
128
129
  """
129
130
  selected_agents = {}
130
-
131
+
131
132
  for agent_name, agent_versions in agents_by_name.items():
132
133
  if not agent_versions:
133
134
  continue
134
-
135
+
135
136
  # If only one version exists, use it
136
137
  if len(agent_versions) == 1:
137
138
  selected_agents[agent_name] = agent_versions[0]
@@ -139,51 +140,61 @@ class MultiSourceAgentDeploymentService:
139
140
  f"Agent '{agent_name}' has single source: {agent_versions[0]['source']}"
140
141
  )
141
142
  continue
142
-
143
+
143
144
  # Compare versions to find the highest
144
145
  highest_version_agent = None
145
146
  highest_version_tuple = (0, 0, 0)
146
-
147
+
147
148
  for agent_info in agent_versions:
148
149
  version_str = agent_info.get("version", "0.0.0")
149
150
  version_tuple = self.version_manager.parse_version(version_str)
150
-
151
+
151
152
  self.logger.debug(
152
153
  f"Agent '{agent_name}' from {agent_info['source']}: "
153
154
  f"version {version_str} -> {version_tuple}"
154
155
  )
155
-
156
+
156
157
  # Compare with current highest
157
- if self.version_manager.compare_versions(version_tuple, highest_version_tuple) > 0:
158
+ if (
159
+ self.version_manager.compare_versions(
160
+ version_tuple, highest_version_tuple
161
+ )
162
+ > 0
163
+ ):
158
164
  highest_version_agent = agent_info
159
165
  highest_version_tuple = version_tuple
160
-
166
+
161
167
  if highest_version_agent:
162
168
  selected_agents[agent_name] = highest_version_agent
163
169
  self.logger.info(
164
170
  f"Selected agent '{agent_name}' version {highest_version_agent['version']} "
165
171
  f"from {highest_version_agent['source']} source"
166
172
  )
167
-
173
+
168
174
  # Log if a higher priority source was overridden by version
169
175
  for other_agent in agent_versions:
170
176
  if other_agent != highest_version_agent:
171
- other_version = self.version_manager.parse_version(
177
+ self.version_manager.parse_version(
172
178
  other_agent.get("version", "0.0.0")
173
179
  )
174
- if other_agent["source"] == "project" and highest_version_agent["source"] == "system":
180
+ if (
181
+ other_agent["source"] == "project"
182
+ and highest_version_agent["source"] == "system"
183
+ ):
175
184
  self.logger.warning(
176
185
  f"Project agent '{agent_name}' v{other_agent['version']} "
177
186
  f"overridden by higher system version v{highest_version_agent['version']}"
178
187
  )
179
- elif other_agent["source"] == "user" and highest_version_agent["source"] in ["system", "project"]:
188
+ elif other_agent["source"] == "user" and highest_version_agent[
189
+ "source"
190
+ ] in ["system", "project"]:
180
191
  self.logger.warning(
181
192
  f"User agent '{agent_name}' v{other_agent['version']} "
182
193
  f"overridden by higher {highest_version_agent['source']} version v{highest_version_agent['version']}"
183
194
  )
184
-
195
+
185
196
  return selected_agents
186
-
197
+
187
198
  def get_agents_for_deployment(
188
199
  self,
189
200
  system_templates_dir: Optional[Path] = None,
@@ -192,10 +203,10 @@ class MultiSourceAgentDeploymentService:
192
203
  working_directory: Optional[Path] = None,
193
204
  excluded_agents: Optional[List[str]] = None,
194
205
  config: Optional[Config] = None,
195
- cleanup_outdated: bool = True
206
+ cleanup_outdated: bool = True,
196
207
  ) -> Tuple[Dict[str, Path], Dict[str, str], Dict[str, Any]]:
197
208
  """Get the highest version agents from all sources for deployment.
198
-
209
+
199
210
  Args:
200
211
  system_templates_dir: Directory containing system agent templates
201
212
  project_agents_dir: Directory containing project-specific agents
@@ -204,7 +215,7 @@ class MultiSourceAgentDeploymentService:
204
215
  excluded_agents: List of agent names to exclude from deployment
205
216
  config: Configuration object for additional filtering
206
217
  cleanup_outdated: Whether to cleanup outdated user agents (default: True)
207
-
218
+
208
219
  Returns:
209
220
  Tuple of:
210
221
  - Dictionary mapping agent names to template file paths
@@ -216,48 +227,52 @@ class MultiSourceAgentDeploymentService:
216
227
  system_templates_dir=system_templates_dir,
217
228
  project_agents_dir=project_agents_dir,
218
229
  user_agents_dir=user_agents_dir,
219
- working_directory=working_directory
230
+ working_directory=working_directory,
220
231
  )
221
-
232
+
222
233
  # Select highest version for each agent
223
234
  selected_agents = self.select_highest_version_agents(agents_by_name)
224
-
235
+
225
236
  # Clean up outdated user agents if enabled
226
237
  cleanup_results = {"removed": [], "preserved": [], "errors": []}
227
238
  if cleanup_outdated:
228
239
  # Check if cleanup is enabled in config or environment
229
240
  cleanup_enabled = True
230
-
241
+
231
242
  # Check environment variable first (for CI/CD and testing)
232
243
  env_cleanup = os.environ.get("CLAUDE_MPM_CLEANUP_USER_AGENTS", "").lower()
233
244
  if env_cleanup in ["false", "0", "no", "disabled"]:
234
245
  cleanup_enabled = False
235
- self.logger.debug("User agent cleanup disabled via environment variable")
236
-
246
+ self.logger.debug(
247
+ "User agent cleanup disabled via environment variable"
248
+ )
249
+
237
250
  # Check config if environment doesn't disable it
238
251
  if cleanup_enabled and config:
239
- cleanup_enabled = config.get("agent_deployment.cleanup_outdated_user_agents", True)
240
-
252
+ cleanup_enabled = config.get(
253
+ "agent_deployment.cleanup_outdated_user_agents", True
254
+ )
255
+
241
256
  if cleanup_enabled:
242
257
  cleanup_results = self.cleanup_outdated_user_agents(
243
258
  agents_by_name, selected_agents
244
259
  )
245
-
260
+
246
261
  # Apply exclusion filters
247
262
  if excluded_agents:
248
263
  for agent_name in excluded_agents:
249
264
  if agent_name in selected_agents:
250
265
  self.logger.info(f"Excluding agent '{agent_name}' from deployment")
251
266
  del selected_agents[agent_name]
252
-
267
+
253
268
  # Apply config-based filtering if provided
254
269
  if config:
255
270
  selected_agents = self._apply_config_filters(selected_agents, config)
256
-
271
+
257
272
  # Create deployment mappings
258
273
  agents_to_deploy = {}
259
274
  agent_sources = {}
260
-
275
+
261
276
  for agent_name, agent_info in selected_agents.items():
262
277
  template_path = Path(agent_info["path"])
263
278
  if template_path.exists():
@@ -265,7 +280,7 @@ class MultiSourceAgentDeploymentService:
265
280
  file_stem = template_path.stem
266
281
  agents_to_deploy[file_stem] = template_path
267
282
  agent_sources[file_stem] = agent_info["source"]
268
-
283
+
269
284
  # Also keep the display name mapping for logging
270
285
  if file_stem != agent_name:
271
286
  self.logger.debug(f"Mapping '{agent_name}' -> '{file_stem}'")
@@ -273,93 +288,92 @@ class MultiSourceAgentDeploymentService:
273
288
  self.logger.warning(
274
289
  f"Template file not found for agent '{agent_name}': {template_path}"
275
290
  )
276
-
291
+
277
292
  self.logger.info(
278
293
  f"Selected {len(agents_to_deploy)} agents for deployment "
279
294
  f"(system: {sum(1 for s in agent_sources.values() if s == 'system')}, "
280
295
  f"project: {sum(1 for s in agent_sources.values() if s == 'project')}, "
281
296
  f"user: {sum(1 for s in agent_sources.values() if s == 'user')})"
282
297
  )
283
-
298
+
284
299
  return agents_to_deploy, agent_sources, cleanup_results
285
-
300
+
286
301
  def cleanup_outdated_user_agents(
287
302
  self,
288
303
  agents_by_name: Dict[str, List[Dict[str, Any]]],
289
- selected_agents: Dict[str, Dict[str, Any]]
304
+ selected_agents: Dict[str, Dict[str, Any]],
290
305
  ) -> Dict[str, Any]:
291
306
  """Remove outdated user agents when project or system agents have higher versions.
292
-
307
+
293
308
  WHY: When project agents are updated to newer versions, outdated user agent
294
309
  copies should be removed to prevent confusion and ensure the latest version
295
310
  is always used. User agents with same or higher versions are preserved to
296
311
  respect user customizations.
297
-
312
+
298
313
  Args:
299
314
  agents_by_name: Dictionary mapping agent names to list of agent info from different sources
300
315
  selected_agents: Dictionary mapping agent names to the selected highest version agent
301
-
316
+
302
317
  Returns:
303
318
  Dictionary with cleanup results:
304
319
  - removed: List of removed agent info
305
320
  - preserved: List of preserved agent info with reasons
306
321
  - errors: List of errors during cleanup
307
322
  """
308
- cleanup_results = {
309
- "removed": [],
310
- "preserved": [],
311
- "errors": []
312
- }
313
-
323
+ cleanup_results = {"removed": [], "preserved": [], "errors": []}
324
+
314
325
  # Get user agents directory
315
326
  user_agents_dir = Path.home() / ".claude-mpm" / "agents"
316
-
327
+
317
328
  # Safety check - only operate on user agents directory
318
329
  if not user_agents_dir.exists():
319
330
  self.logger.debug("User agents directory does not exist, no cleanup needed")
320
331
  return cleanup_results
321
-
332
+
322
333
  for agent_name, agent_versions in agents_by_name.items():
323
334
  # Skip if only one version exists
324
335
  if len(agent_versions) < 2:
325
336
  continue
326
-
337
+
327
338
  selected = selected_agents.get(agent_name)
328
339
  if not selected:
329
340
  continue
330
-
341
+
331
342
  # Process each version of this agent
332
343
  for agent_info in agent_versions:
333
344
  # Only consider user agents for cleanup
334
345
  if agent_info["source"] != "user":
335
346
  continue
336
-
347
+
337
348
  # Safety check - ensure path is within user agents directory
338
349
  user_agent_path = Path(agent_info["path"])
339
350
  try:
340
351
  # Resolve paths to compare them safely
341
352
  resolved_user_path = user_agent_path.resolve()
342
353
  resolved_user_agents_dir = user_agents_dir.resolve()
343
-
354
+
344
355
  # Verify the agent is actually in the user agents directory
345
- if not str(resolved_user_path).startswith(str(resolved_user_agents_dir)):
356
+ if not str(resolved_user_path).startswith(
357
+ str(resolved_user_agents_dir)
358
+ ):
346
359
  self.logger.warning(
347
360
  f"Skipping cleanup for {agent_name}: path {user_agent_path} "
348
361
  f"is not within user agents directory"
349
362
  )
350
- cleanup_results["errors"].append({
351
- "agent": agent_name,
352
- "error": "Path outside user agents directory"
353
- })
363
+ cleanup_results["errors"].append(
364
+ {
365
+ "agent": agent_name,
366
+ "error": "Path outside user agents directory",
367
+ }
368
+ )
354
369
  continue
355
370
  except Exception as e:
356
371
  self.logger.error(f"Error resolving paths for {agent_name}: {e}")
357
- cleanup_results["errors"].append({
358
- "agent": agent_name,
359
- "error": f"Path resolution error: {e}"
360
- })
372
+ cleanup_results["errors"].append(
373
+ {"agent": agent_name, "error": f"Path resolution error: {e}"}
374
+ )
361
375
  continue
362
-
376
+
363
377
  # Compare versions
364
378
  user_version = self.version_manager.parse_version(
365
379
  agent_info.get("version", "0.0.0")
@@ -367,13 +381,16 @@ class MultiSourceAgentDeploymentService:
367
381
  selected_version = self.version_manager.parse_version(
368
382
  selected.get("version", "0.0.0")
369
383
  )
370
-
384
+
371
385
  version_comparison = self.version_manager.compare_versions(
372
386
  user_version, selected_version
373
387
  )
374
-
388
+
375
389
  # Determine action based on version comparison and selected source
376
- if version_comparison < 0 and selected["source"] in ["project", "system"]:
390
+ if version_comparison < 0 and selected["source"] in [
391
+ "project",
392
+ "system",
393
+ ]:
377
394
  # User agent has lower version than selected project/system agent - remove it
378
395
  if user_agent_path.exists():
379
396
  try:
@@ -384,30 +401,32 @@ class MultiSourceAgentDeploymentService:
384
401
  f"(superseded by {selected['source']} "
385
402
  f"v{self.version_manager.format_version_display(selected_version)})"
386
403
  )
387
-
404
+
388
405
  # Remove the file
389
406
  user_agent_path.unlink()
390
-
391
- cleanup_results["removed"].append({
392
- "name": agent_name,
393
- "version": self.version_manager.format_version_display(user_version),
394
- "path": str(user_agent_path),
395
- "reason": f"Superseded by {selected['source']} v{self.version_manager.format_version_display(selected_version)}"
396
- })
407
+
408
+ cleanup_results["removed"].append(
409
+ {
410
+ "name": agent_name,
411
+ "version": self.version_manager.format_version_display(
412
+ user_version
413
+ ),
414
+ "path": str(user_agent_path),
415
+ "reason": f"Superseded by {selected['source']} v{self.version_manager.format_version_display(selected_version)}",
416
+ }
417
+ )
397
418
  except PermissionError as e:
398
419
  error_msg = f"Permission denied removing {agent_name}: {e}"
399
420
  self.logger.error(error_msg)
400
- cleanup_results["errors"].append({
401
- "agent": agent_name,
402
- "error": error_msg
403
- })
421
+ cleanup_results["errors"].append(
422
+ {"agent": agent_name, "error": error_msg}
423
+ )
404
424
  except Exception as e:
405
425
  error_msg = f"Error removing {agent_name}: {e}"
406
426
  self.logger.error(error_msg)
407
- cleanup_results["errors"].append({
408
- "agent": agent_name,
409
- "error": error_msg
410
- })
427
+ cleanup_results["errors"].append(
428
+ {"agent": agent_name, "error": error_msg}
429
+ )
411
430
  else:
412
431
  # Preserve the user agent
413
432
  if version_comparison >= 0:
@@ -416,18 +435,22 @@ class MultiSourceAgentDeploymentService:
416
435
  reason = "User agent is the selected version"
417
436
  else:
418
437
  reason = "User customization preserved"
419
-
420
- cleanup_results["preserved"].append({
421
- "name": agent_name,
422
- "version": self.version_manager.format_version_display(user_version),
423
- "reason": reason
424
- })
425
-
438
+
439
+ cleanup_results["preserved"].append(
440
+ {
441
+ "name": agent_name,
442
+ "version": self.version_manager.format_version_display(
443
+ user_version
444
+ ),
445
+ "reason": reason,
446
+ }
447
+ )
448
+
426
449
  self.logger.debug(
427
450
  f"Preserving user agent {agent_name} "
428
451
  f"v{self.version_manager.format_version_display(user_version)}: {reason}"
429
452
  )
430
-
453
+
431
454
  # Log cleanup summary
432
455
  if cleanup_results["removed"]:
433
456
  self.logger.info(
@@ -441,65 +464,67 @@ class MultiSourceAgentDeploymentService:
441
464
  self.logger.warning(
442
465
  f"Encountered {len(cleanup_results['errors'])} errors during cleanup"
443
466
  )
444
-
467
+
445
468
  return cleanup_results
446
-
469
+
447
470
  def _apply_config_filters(
448
- self,
449
- selected_agents: Dict[str, Dict[str, Any]],
450
- config: Config
471
+ self, selected_agents: Dict[str, Dict[str, Any]], config: Config
451
472
  ) -> Dict[str, Dict[str, Any]]:
452
473
  """Apply configuration-based filtering to selected agents.
453
-
474
+
454
475
  Args:
455
476
  selected_agents: Dictionary of selected agents
456
477
  config: Configuration object
457
-
478
+
458
479
  Returns:
459
480
  Filtered dictionary of agents
460
481
  """
461
482
  filtered_agents = {}
462
-
483
+
463
484
  # Get exclusion patterns from config
464
485
  exclusion_patterns = config.get("agent_deployment.exclusion_patterns", [])
465
-
486
+
466
487
  # Get environment-specific exclusions
467
488
  environment = config.get("environment", "development")
468
489
  env_exclusions = config.get(f"agent_deployment.{environment}_exclusions", [])
469
-
490
+
470
491
  for agent_name, agent_info in selected_agents.items():
471
492
  # Check exclusion patterns
472
493
  excluded = False
473
-
494
+
474
495
  for pattern in exclusion_patterns:
475
496
  if pattern in agent_name:
476
- self.logger.debug(f"Excluding '{agent_name}' due to pattern '{pattern}'")
497
+ self.logger.debug(
498
+ f"Excluding '{agent_name}' due to pattern '{pattern}'"
499
+ )
477
500
  excluded = True
478
501
  break
479
-
502
+
480
503
  # Check environment exclusions
481
504
  if not excluded and agent_name in env_exclusions:
482
- self.logger.debug(f"Excluding '{agent_name}' due to {environment} environment")
505
+ self.logger.debug(
506
+ f"Excluding '{agent_name}' due to {environment} environment"
507
+ )
483
508
  excluded = True
484
-
509
+
485
510
  if not excluded:
486
511
  filtered_agents[agent_name] = agent_info
487
-
512
+
488
513
  return filtered_agents
489
-
514
+
490
515
  def compare_deployed_versions(
491
516
  self,
492
517
  deployed_agents_dir: Path,
493
518
  agents_to_deploy: Dict[str, Path],
494
- agent_sources: Dict[str, str]
519
+ agent_sources: Dict[str, str],
495
520
  ) -> Dict[str, Any]:
496
521
  """Compare deployed agent versions with candidates for deployment.
497
-
522
+
498
523
  Args:
499
524
  deployed_agents_dir: Directory containing currently deployed agents
500
525
  agents_to_deploy: Dictionary mapping agent names to template paths
501
526
  agent_sources: Dictionary mapping agent names to their sources
502
-
527
+
503
528
  Returns:
504
529
  Dictionary with comparison results including which agents need updates
505
530
  """
@@ -510,111 +535,142 @@ class MultiSourceAgentDeploymentService:
510
535
  "orphaned_agents": [], # Agents without templates
511
536
  "version_upgrades": [],
512
537
  "version_downgrades": [],
513
- "source_changes": []
538
+ "source_changes": [],
514
539
  }
515
-
540
+
516
541
  for agent_name, template_path in agents_to_deploy.items():
517
542
  deployed_file = deployed_agents_dir / f"{agent_name}.md"
518
-
543
+
519
544
  if not deployed_file.exists():
520
- comparison_results["new_agents"].append({
521
- "name": agent_name,
522
- "source": agent_sources[agent_name],
523
- "template": str(template_path)
524
- })
545
+ comparison_results["new_agents"].append(
546
+ {
547
+ "name": agent_name,
548
+ "source": agent_sources[agent_name],
549
+ "template": str(template_path),
550
+ }
551
+ )
525
552
  comparison_results["needs_update"].append(agent_name)
526
553
  continue
527
-
554
+
528
555
  # Read template version
529
556
  try:
530
557
  template_data = json.loads(template_path.read_text())
531
558
  metadata = template_data.get("metadata", {})
532
559
  template_version = self.version_manager.parse_version(
533
- template_data.get("agent_version") or
534
- template_data.get("version") or
535
- metadata.get("version", "0.0.0")
560
+ template_data.get("agent_version")
561
+ or template_data.get("version")
562
+ or metadata.get("version", "0.0.0")
536
563
  )
537
564
  except Exception as e:
538
565
  self.logger.warning(f"Error reading template for '{agent_name}': {e}")
539
566
  continue
540
-
567
+
541
568
  # Read deployed version
542
569
  try:
543
570
  deployed_content = deployed_file.read_text()
544
- deployed_version, _, _ = self.version_manager.extract_version_from_frontmatter(
545
- deployed_content
571
+ deployed_version, _, _ = (
572
+ self.version_manager.extract_version_from_frontmatter(
573
+ deployed_content
574
+ )
546
575
  )
547
-
576
+
548
577
  # Extract source from deployed agent if available
549
578
  deployed_source = "unknown"
550
579
  if "source:" in deployed_content:
551
580
  import re
552
- source_match = re.search(r'^source:\s*(.+)$', deployed_content, re.MULTILINE)
581
+
582
+ source_match = re.search(
583
+ r"^source:\s*(.+)$", deployed_content, re.MULTILINE
584
+ )
553
585
  if source_match:
554
586
  deployed_source = source_match.group(1).strip()
555
-
587
+
556
588
  # If source is still unknown, try to infer it from deployment context
557
589
  if deployed_source == "unknown":
558
- deployed_source = self._infer_agent_source_from_context(agent_name, deployed_agents_dir)
590
+ deployed_source = self._infer_agent_source_from_context(
591
+ agent_name, deployed_agents_dir
592
+ )
559
593
  except Exception as e:
560
594
  self.logger.warning(f"Error reading deployed agent '{agent_name}': {e}")
561
595
  comparison_results["needs_update"].append(agent_name)
562
596
  continue
563
-
597
+
564
598
  # Compare versions
565
599
  version_comparison = self.version_manager.compare_versions(
566
600
  template_version, deployed_version
567
601
  )
568
-
602
+
569
603
  if version_comparison > 0:
570
604
  # Template version is higher
571
- comparison_results["version_upgrades"].append({
572
- "name": agent_name,
573
- "deployed_version": self.version_manager.format_version_display(deployed_version),
574
- "new_version": self.version_manager.format_version_display(template_version),
575
- "source": agent_sources[agent_name],
576
- "previous_source": deployed_source
577
- })
605
+ comparison_results["version_upgrades"].append(
606
+ {
607
+ "name": agent_name,
608
+ "deployed_version": self.version_manager.format_version_display(
609
+ deployed_version
610
+ ),
611
+ "new_version": self.version_manager.format_version_display(
612
+ template_version
613
+ ),
614
+ "source": agent_sources[agent_name],
615
+ "previous_source": deployed_source,
616
+ }
617
+ )
578
618
  comparison_results["needs_update"].append(agent_name)
579
-
619
+
580
620
  if deployed_source != agent_sources[agent_name]:
581
- comparison_results["source_changes"].append({
582
- "name": agent_name,
583
- "from_source": deployed_source,
584
- "to_source": agent_sources[agent_name]
585
- })
621
+ comparison_results["source_changes"].append(
622
+ {
623
+ "name": agent_name,
624
+ "from_source": deployed_source,
625
+ "to_source": agent_sources[agent_name],
626
+ }
627
+ )
586
628
  elif version_comparison < 0:
587
629
  # Deployed version is higher (shouldn't happen with proper version management)
588
- comparison_results["version_downgrades"].append({
589
- "name": agent_name,
590
- "deployed_version": self.version_manager.format_version_display(deployed_version),
591
- "template_version": self.version_manager.format_version_display(template_version),
592
- "warning": "Deployed version is higher than template"
593
- })
630
+ comparison_results["version_downgrades"].append(
631
+ {
632
+ "name": agent_name,
633
+ "deployed_version": self.version_manager.format_version_display(
634
+ deployed_version
635
+ ),
636
+ "template_version": self.version_manager.format_version_display(
637
+ template_version
638
+ ),
639
+ "warning": "Deployed version is higher than template",
640
+ }
641
+ )
594
642
  # Don't add to needs_update - keep the higher version
595
643
  else:
596
644
  # Versions are equal
597
- comparison_results["up_to_date"].append({
598
- "name": agent_name,
599
- "version": self.version_manager.format_version_display(deployed_version),
600
- "source": agent_sources[agent_name]
601
- })
602
-
645
+ comparison_results["up_to_date"].append(
646
+ {
647
+ "name": agent_name,
648
+ "version": self.version_manager.format_version_display(
649
+ deployed_version
650
+ ),
651
+ "source": agent_sources[agent_name],
652
+ }
653
+ )
654
+
603
655
  # Check for orphaned agents (deployed but no template)
604
- orphaned = self._detect_orphaned_agents_simple(deployed_agents_dir, agents_to_deploy)
656
+ orphaned = self._detect_orphaned_agents_simple(
657
+ deployed_agents_dir, agents_to_deploy
658
+ )
605
659
  comparison_results["orphaned_agents"] = orphaned
606
-
660
+
607
661
  # Log summary
608
662
  summary_parts = [
609
663
  f"{len(comparison_results['needs_update'])} need updates",
610
664
  f"{len(comparison_results['up_to_date'])} up to date",
611
- f"{len(comparison_results['new_agents'])} new agents"
665
+ f"{len(comparison_results['new_agents'])} new agents",
612
666
  ]
613
667
  if comparison_results["orphaned_agents"]:
614
- summary_parts.append(f"{len(comparison_results['orphaned_agents'])} orphaned")
615
-
668
+ summary_parts.append(
669
+ f"{len(comparison_results['orphaned_agents'])} orphaned"
670
+ )
671
+
616
672
  self.logger.info(f"Version comparison complete: {', '.join(summary_parts)}")
617
-
673
+
618
674
  if comparison_results["version_upgrades"]:
619
675
  for upgrade in comparison_results["version_upgrades"]:
620
676
  self.logger.info(
@@ -622,14 +678,14 @@ class MultiSourceAgentDeploymentService:
622
678
  f"{upgrade['deployed_version']} -> {upgrade['new_version']} "
623
679
  f"(from {upgrade['source']})"
624
680
  )
625
-
681
+
626
682
  if comparison_results["source_changes"]:
627
683
  for change in comparison_results["source_changes"]:
628
684
  self.logger.info(
629
685
  f" Source change: {change['name']} "
630
686
  f"from {change['from_source']} to {change['to_source']}"
631
687
  )
632
-
688
+
633
689
  if comparison_results["version_downgrades"]:
634
690
  for downgrade in comparison_results["version_downgrades"]:
635
691
  # Changed from warning to debug - deployed versions higher than templates
@@ -639,7 +695,7 @@ class MultiSourceAgentDeploymentService:
639
695
  f"{downgrade['deployed_version']} is higher than template "
640
696
  f"{downgrade['template_version']} (keeping deployed version)"
641
697
  )
642
-
698
+
643
699
  # Log orphaned agents if found
644
700
  if comparison_results["orphaned_agents"]:
645
701
  self.logger.info(
@@ -651,57 +707,72 @@ class MultiSourceAgentDeploymentService:
651
707
  f" - {orphan['name']} v{orphan['version']} "
652
708
  f"(consider removing or creating a template)"
653
709
  )
654
-
710
+
655
711
  return comparison_results
656
-
657
- def _infer_agent_source_from_context(self, agent_name: str, deployed_agents_dir: Path) -> str:
712
+
713
+ def _infer_agent_source_from_context(
714
+ self, agent_name: str, deployed_agents_dir: Path
715
+ ) -> str:
658
716
  """Infer the source of a deployed agent when source metadata is missing.
659
-
717
+
660
718
  This method attempts to determine the agent source based on:
661
719
  1. Deployment context (development vs pipx)
662
720
  2. Agent naming patterns
663
721
  3. Known system agents
664
-
722
+
665
723
  Args:
666
724
  agent_name: Name of the agent
667
725
  deployed_agents_dir: Directory where agent is deployed
668
-
726
+
669
727
  Returns:
670
728
  Inferred source string (system/project/user)
671
729
  """
672
730
  # List of known system agents that ship with claude-mpm
673
731
  system_agents = {
674
- "pm", "engineer", "qa", "research", "documentation", "ops",
675
- "security", "web-ui", "api-qa", "version-control"
732
+ "pm",
733
+ "engineer",
734
+ "qa",
735
+ "research",
736
+ "documentation",
737
+ "ops",
738
+ "security",
739
+ "web-ui",
740
+ "api-qa",
741
+ "version-control",
676
742
  }
677
-
743
+
678
744
  # If this is a known system agent, it's from system
679
745
  if agent_name in system_agents:
680
746
  return "system"
681
-
747
+
682
748
  # Check deployment context
683
749
  from ....core.unified_paths import get_path_manager
750
+
684
751
  path_manager = get_path_manager()
685
-
752
+
686
753
  # If deployed_agents_dir is under user home/.claude/agents, check context
687
754
  user_claude_dir = Path.home() / ".claude" / "agents"
688
755
  if deployed_agents_dir == user_claude_dir:
689
756
  # Check if we're in development mode
690
757
  try:
691
758
  from ....core.unified_paths import DeploymentContext, PathContext
759
+
692
760
  deployment_context = PathContext.detect_deployment_context()
693
-
694
- if deployment_context in (DeploymentContext.DEVELOPMENT, DeploymentContext.EDITABLE_INSTALL):
761
+
762
+ if deployment_context in (
763
+ DeploymentContext.DEVELOPMENT,
764
+ DeploymentContext.EDITABLE_INSTALL,
765
+ ):
695
766
  # In development mode, unknown agents are likely system agents being tested
696
767
  return "system"
697
- elif deployment_context == DeploymentContext.PIPX_INSTALL:
768
+ if deployment_context == DeploymentContext.PIPX_INSTALL:
698
769
  # In pipx mode, unknown agents could be system agents
699
770
  # Check if agent follows system naming patterns
700
- if agent_name.count('-') <= 2 and len(agent_name) <= 20:
771
+ if agent_name.count("-") <= 2 and len(agent_name) <= 20:
701
772
  return "system"
702
773
  except Exception:
703
774
  pass
704
-
775
+
705
776
  # Check if deployed to project-specific directory
706
777
  try:
707
778
  project_root = path_manager.project_root
@@ -709,165 +780,163 @@ class MultiSourceAgentDeploymentService:
709
780
  return "project"
710
781
  except Exception:
711
782
  pass
712
-
783
+
713
784
  # Default inference based on naming patterns
714
785
  # System agents typically have simple names
715
- if '-' not in agent_name or agent_name.count('-') <= 1:
786
+ if "-" not in agent_name or agent_name.count("-") <= 1:
716
787
  return "system"
717
-
788
+
718
789
  # Complex names are more likely to be user/project agents
719
790
  return "user"
720
-
791
+
721
792
  def detect_orphaned_agents(
722
- self,
723
- deployed_agents_dir: Path,
724
- available_agents: Dict[str, Any]
793
+ self, deployed_agents_dir: Path, available_agents: Dict[str, Any]
725
794
  ) -> List[Dict[str, Any]]:
726
795
  """Detect deployed agents that don't have corresponding templates.
727
-
796
+
728
797
  WHY: Orphaned agents can cause confusion with version warnings.
729
798
  This method identifies them so they can be handled appropriately.
730
-
799
+
731
800
  Args:
732
801
  deployed_agents_dir: Directory containing deployed agents
733
802
  available_agents: Dictionary of available agents from all sources
734
-
803
+
735
804
  Returns:
736
805
  List of orphaned agent information
737
806
  """
738
807
  orphaned = []
739
-
808
+
740
809
  if not deployed_agents_dir.exists():
741
810
  return orphaned
742
-
811
+
743
812
  # Build a mapping of file stems to agent names for comparison
744
813
  # Since available_agents uses display names like "Code Analysis Agent"
745
814
  # but deployed files use stems like "code_analyzer"
746
815
  available_stems = set()
747
816
  stem_to_name = {}
748
-
817
+
749
818
  for agent_name, agent_sources in available_agents.items():
750
819
  # Get the file path from the first source to extract the stem
751
- if agent_sources and isinstance(agent_sources, list) and len(agent_sources) > 0:
820
+ if (
821
+ agent_sources
822
+ and isinstance(agent_sources, list)
823
+ and len(agent_sources) > 0
824
+ ):
752
825
  first_source = agent_sources[0]
753
- if 'file_path' in first_source:
754
- file_path = Path(first_source['file_path'])
826
+ if "file_path" in first_source:
827
+ file_path = Path(first_source["file_path"])
755
828
  stem = file_path.stem
756
829
  available_stems.add(stem)
757
830
  stem_to_name[stem] = agent_name
758
-
831
+
759
832
  for deployed_file in deployed_agents_dir.glob("*.md"):
760
833
  agent_stem = deployed_file.stem
761
-
834
+
762
835
  # Skip if this agent has a template (check by stem, not display name)
763
836
  if agent_stem in available_stems:
764
837
  continue
765
-
838
+
766
839
  # This is an orphaned agent
767
840
  try:
768
841
  deployed_content = deployed_file.read_text()
769
- deployed_version, _, _ = self.version_manager.extract_version_from_frontmatter(
770
- deployed_content
842
+ deployed_version, _, _ = (
843
+ self.version_manager.extract_version_from_frontmatter(
844
+ deployed_content
845
+ )
846
+ )
847
+ version_str = self.version_manager.format_version_display(
848
+ deployed_version
771
849
  )
772
- version_str = self.version_manager.format_version_display(deployed_version)
773
850
  except Exception:
774
851
  version_str = "unknown"
775
-
776
- orphaned.append({
777
- "name": agent_stem,
778
- "file": str(deployed_file),
779
- "version": version_str
780
- })
781
-
852
+
853
+ orphaned.append(
854
+ {"name": agent_stem, "file": str(deployed_file), "version": version_str}
855
+ )
856
+
782
857
  return orphaned
783
-
858
+
784
859
  def _detect_orphaned_agents_simple(
785
- self,
786
- deployed_agents_dir: Path,
787
- agents_to_deploy: Dict[str, Path]
860
+ self, deployed_agents_dir: Path, agents_to_deploy: Dict[str, Path]
788
861
  ) -> List[Dict[str, Any]]:
789
862
  """Simple orphan detection that works with agents_to_deploy structure.
790
-
863
+
791
864
  Args:
792
865
  deployed_agents_dir: Directory containing deployed agents
793
866
  agents_to_deploy: Dictionary mapping file stems to template paths
794
-
867
+
795
868
  Returns:
796
869
  List of orphaned agent information
797
870
  """
798
871
  orphaned = []
799
-
872
+
800
873
  if not deployed_agents_dir.exists():
801
874
  return orphaned
802
-
875
+
803
876
  # agents_to_deploy already contains file stems as keys
804
877
  available_stems = set(agents_to_deploy.keys())
805
-
878
+
806
879
  for deployed_file in deployed_agents_dir.glob("*.md"):
807
880
  agent_stem = deployed_file.stem
808
-
881
+
809
882
  # Skip if this agent has a template (check by stem)
810
883
  if agent_stem in available_stems:
811
884
  continue
812
-
885
+
813
886
  # This is an orphaned agent
814
887
  try:
815
888
  deployed_content = deployed_file.read_text()
816
- deployed_version, _, _ = self.version_manager.extract_version_from_frontmatter(
817
- deployed_content
889
+ deployed_version, _, _ = (
890
+ self.version_manager.extract_version_from_frontmatter(
891
+ deployed_content
892
+ )
893
+ )
894
+ version_str = self.version_manager.format_version_display(
895
+ deployed_version
818
896
  )
819
- version_str = self.version_manager.format_version_display(deployed_version)
820
897
  except Exception:
821
898
  version_str = "unknown"
822
-
823
- orphaned.append({
824
- "name": agent_stem,
825
- "file": str(deployed_file),
826
- "version": version_str
827
- })
828
-
899
+
900
+ orphaned.append(
901
+ {"name": agent_stem, "file": str(deployed_file), "version": version_str}
902
+ )
903
+
829
904
  return orphaned
830
-
905
+
831
906
  def cleanup_orphaned_agents(
832
- self,
833
- deployed_agents_dir: Path,
834
- dry_run: bool = True
907
+ self, deployed_agents_dir: Path, dry_run: bool = True
835
908
  ) -> Dict[str, Any]:
836
909
  """Clean up orphaned agents that don't have templates.
837
-
910
+
838
911
  WHY: Orphaned agents can accumulate over time and cause confusion.
839
912
  This method provides a way to clean them up systematically.
840
-
913
+
841
914
  Args:
842
915
  deployed_agents_dir: Directory containing deployed agents
843
916
  dry_run: If True, only report what would be removed
844
-
917
+
845
918
  Returns:
846
919
  Dictionary with cleanup results
847
920
  """
848
- results = {
849
- "orphaned": [],
850
- "removed": [],
851
- "errors": []
852
- }
853
-
921
+ results = {"orphaned": [], "removed": [], "errors": []}
922
+
854
923
  # First, discover all available agents from all sources
855
924
  all_agents = self.discover_agents_from_all_sources()
856
- available_names = set(all_agents.keys())
857
-
925
+ set(all_agents.keys())
926
+
858
927
  # Detect orphaned agents
859
928
  orphaned = self.detect_orphaned_agents(deployed_agents_dir, all_agents)
860
929
  results["orphaned"] = orphaned
861
-
930
+
862
931
  if not orphaned:
863
932
  self.logger.info("No orphaned agents found")
864
933
  return results
865
-
934
+
866
935
  self.logger.info(f"Found {len(orphaned)} orphaned agent(s)")
867
-
936
+
868
937
  for orphan in orphaned:
869
938
  agent_file = Path(orphan["file"])
870
-
939
+
871
940
  if dry_run:
872
941
  self.logger.info(
873
942
  f" Would remove: {orphan['name']} v{orphan['version']}"
@@ -883,10 +952,10 @@ class MultiSourceAgentDeploymentService:
883
952
  error_msg = f"Failed to remove {orphan['name']}: {e}"
884
953
  results["errors"].append(error_msg)
885
954
  self.logger.error(f" {error_msg}")
886
-
955
+
887
956
  if dry_run and orphaned:
888
957
  self.logger.info(
889
958
  "Run with dry_run=False to actually remove orphaned agents"
890
959
  )
891
-
892
- return results
960
+
961
+ return results