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
@@ -0,0 +1,605 @@
1
+ """Agent output formatting service for CLI commands.
2
+
3
+ WHY: This service extracts output formatting logic from agents.py to reduce
4
+ duplication, improve maintainability, and provide consistent formatting across
5
+ all agent-related CLI commands. Following SOLID principles, this service has
6
+ a single responsibility: formatting agent data for display.
7
+
8
+ DESIGN DECISIONS:
9
+ - Interface-based design for dependency injection and testability
10
+ - Single responsibility: output formatting only
11
+ - Support for multiple formats (json, yaml, table, text)
12
+ - Quiet and verbose mode handling
13
+ - Reusable across all agent commands
14
+ - Consistent formatting patterns
15
+ """
16
+
17
+ import json
18
+ from abc import ABC, abstractmethod
19
+ from typing import Any, Dict, List
20
+
21
+ import yaml
22
+
23
+ from claude_mpm.core.logger import get_logger
24
+
25
+
26
+ # Interface Definition
27
+ class IAgentOutputFormatter(ABC):
28
+ """Interface for agent output formatting service."""
29
+
30
+ @abstractmethod
31
+ def format_agent_list(
32
+ self,
33
+ agents: List[Dict[str, Any]],
34
+ output_format: str = "text",
35
+ verbose: bool = False,
36
+ quiet: bool = False,
37
+ ) -> str:
38
+ """Format list of agents for display."""
39
+
40
+ @abstractmethod
41
+ def format_agent_details(
42
+ self, agent: Dict[str, Any], output_format: str = "text", verbose: bool = False
43
+ ) -> str:
44
+ """Format single agent details."""
45
+
46
+ @abstractmethod
47
+ def format_dependency_report(
48
+ self,
49
+ dependencies: Dict[str, Any],
50
+ output_format: str = "text",
51
+ show_status: bool = True,
52
+ ) -> str:
53
+ """Format dependency information."""
54
+
55
+ @abstractmethod
56
+ def format_deployment_result(
57
+ self, result: Dict[str, Any], output_format: str = "text", verbose: bool = False
58
+ ) -> str:
59
+ """Format deployment results."""
60
+
61
+ @abstractmethod
62
+ def format_cleanup_result(
63
+ self, result: Dict[str, Any], output_format: str = "text", dry_run: bool = False
64
+ ) -> str:
65
+ """Format cleanup results."""
66
+
67
+ @abstractmethod
68
+ def format_as_json(self, data: Any, pretty: bool = True) -> str:
69
+ """Format data as JSON."""
70
+
71
+ @abstractmethod
72
+ def format_as_yaml(self, data: Any) -> str:
73
+ """Format data as YAML."""
74
+
75
+ @abstractmethod
76
+ def format_as_table(
77
+ self, headers: List[str], rows: List[List[str]], min_column_width: int = 10
78
+ ) -> str:
79
+ """Format data as table."""
80
+
81
+ @abstractmethod
82
+ def format_agents_by_tier(
83
+ self, agents_by_tier: Dict[str, List[str]], output_format: str = "text"
84
+ ) -> str:
85
+ """Format agents grouped by tier."""
86
+
87
+ @abstractmethod
88
+ def format_fix_result(
89
+ self, result: Dict[str, Any], output_format: str = "text"
90
+ ) -> str:
91
+ """Format fix operation results."""
92
+
93
+
94
+ class AgentOutputFormatter(IAgentOutputFormatter):
95
+ """Implementation of agent output formatting service.
96
+
97
+ WHY: Centralizes all agent output formatting logic to ensure consistency
98
+ and reduce code duplication across agent commands.
99
+ """
100
+
101
+ def __init__(self):
102
+ """Initialize the formatter."""
103
+ self.logger = get_logger(self.__class__.__name__)
104
+
105
+ def format_agent_list(
106
+ self,
107
+ agents: List[Dict[str, Any]],
108
+ output_format: str = "text",
109
+ verbose: bool = False,
110
+ quiet: bool = False,
111
+ ) -> str:
112
+ """Format list of agents for display.
113
+
114
+ Args:
115
+ agents: List of agent dictionaries
116
+ output_format: Output format (text, json, yaml, table)
117
+ verbose: Include extra details
118
+ quiet: Minimal output
119
+
120
+ Returns:
121
+ Formatted string for display
122
+ """
123
+ if output_format == "json":
124
+ return self.format_as_json({"agents": agents, "count": len(agents)})
125
+ if output_format == "yaml":
126
+ return self.format_as_yaml({"agents": agents, "count": len(agents)})
127
+ if output_format == "table":
128
+ return self._format_agents_as_table(agents, verbose, quiet)
129
+ # text format
130
+ return self._format_agents_as_text(agents, verbose, quiet)
131
+
132
+ def format_agent_details(
133
+ self, agent: Dict[str, Any], output_format: str = "text", verbose: bool = False
134
+ ) -> str:
135
+ """Format single agent details.
136
+
137
+ Args:
138
+ agent: Agent dictionary with details
139
+ output_format: Output format (text, json, yaml)
140
+ verbose: Include extra details
141
+
142
+ Returns:
143
+ Formatted string for display
144
+ """
145
+ if output_format == "json":
146
+ return self.format_as_json(agent)
147
+ if output_format == "yaml":
148
+ return self.format_as_yaml(agent)
149
+ # text format
150
+ lines = []
151
+ lines.append(f"Agent: {agent.get('name', 'Unknown')}")
152
+ lines.append("-" * 40)
153
+
154
+ # Basic info
155
+ for key in ["file", "path", "version", "description", "tier"]:
156
+ if key in agent:
157
+ lines.append(f"{key.capitalize()}: {agent[key]}")
158
+
159
+ # Specializations
160
+ if agent.get("specializations"):
161
+ lines.append(f"Specializations: {', '.join(agent['specializations'])}")
162
+
163
+ # Verbose mode additions
164
+ if verbose:
165
+ if "dependencies" in agent:
166
+ lines.append("\nDependencies:")
167
+ deps = agent["dependencies"]
168
+ if deps.get("python"):
169
+ lines.append(f" Python: {', '.join(deps['python'])}")
170
+ if deps.get("system"):
171
+ lines.append(f" System: {', '.join(deps['system'])}")
172
+
173
+ if "metadata" in agent:
174
+ lines.append("\nMetadata:")
175
+ for k, v in agent["metadata"].items():
176
+ lines.append(f" {k}: {v}")
177
+
178
+ return "\n".join(lines)
179
+
180
+ def format_dependency_report(
181
+ self,
182
+ dependencies: Dict[str, Any],
183
+ output_format: str = "text",
184
+ show_status: bool = True,
185
+ ) -> str:
186
+ """Format dependency information.
187
+
188
+ Args:
189
+ dependencies: Dictionary with dependency info
190
+ output_format: Output format (text, json, yaml)
191
+ show_status: Show installation status
192
+
193
+ Returns:
194
+ Formatted string for display
195
+ """
196
+ if output_format == "json":
197
+ return self.format_as_json(dependencies)
198
+ if output_format == "yaml":
199
+ return self.format_as_yaml(dependencies)
200
+ # text format
201
+ lines = []
202
+ lines.append("Agent Dependencies:")
203
+ lines.append("-" * 40)
204
+
205
+ # Python dependencies
206
+ if dependencies.get("python"):
207
+ lines.append(f"\nPython Dependencies ({len(dependencies['python'])}):")
208
+ for dep in dependencies["python"]:
209
+ if show_status and isinstance(dep, dict):
210
+ status = "āœ“" if dep.get("installed") else "āœ—"
211
+ lines.append(f" {status} {dep.get('name', dep)}")
212
+ else:
213
+ lines.append(f" - {dep}")
214
+
215
+ # System dependencies
216
+ if dependencies.get("system"):
217
+ lines.append(f"\nSystem Dependencies ({len(dependencies['system'])}):")
218
+ for dep in dependencies["system"]:
219
+ if show_status and isinstance(dep, dict):
220
+ status = "āœ“" if dep.get("installed") else "āœ—"
221
+ lines.append(f" {status} {dep.get('name', dep)}")
222
+ else:
223
+ lines.append(f" - {dep}")
224
+
225
+ # Missing dependencies
226
+ if "missing" in dependencies:
227
+ if dependencies["missing"].get("python"):
228
+ lines.append(
229
+ f"\nāŒ Missing Python: {len(dependencies['missing']['python'])}"
230
+ )
231
+ for dep in dependencies["missing"]["python"][:5]:
232
+ lines.append(f" - {dep}")
233
+ if len(dependencies["missing"]["python"]) > 5:
234
+ lines.append(
235
+ f" ... and {len(dependencies['missing']['python']) - 5} more"
236
+ )
237
+
238
+ if dependencies["missing"].get("system"):
239
+ lines.append(
240
+ f"\nāŒ Missing System: {len(dependencies['missing']['system'])}"
241
+ )
242
+ for dep in dependencies["missing"]["system"]:
243
+ lines.append(f" - {dep}")
244
+
245
+ return "\n".join(lines)
246
+
247
+ def format_deployment_result(
248
+ self, result: Dict[str, Any], output_format: str = "text", verbose: bool = False
249
+ ) -> str:
250
+ """Format deployment results.
251
+
252
+ Args:
253
+ result: Deployment result dictionary
254
+ output_format: Output format (text, json, yaml)
255
+ verbose: Include extra details
256
+
257
+ Returns:
258
+ Formatted string for display
259
+ """
260
+ if output_format == "json":
261
+ return self.format_as_json(result)
262
+ if output_format == "yaml":
263
+ return self.format_as_yaml(result)
264
+ # text format
265
+ lines = []
266
+
267
+ # Deployed agents
268
+ deployed_count = result.get("deployed_count", 0)
269
+ if deployed_count > 0:
270
+ lines.append(f"āœ“ Deployed {deployed_count} agents")
271
+ if verbose and "deployed" in result:
272
+ for agent in result["deployed"]:
273
+ lines.append(f" - {agent.get('name', agent)}")
274
+
275
+ # Updated agents
276
+ updated_count = result.get("updated_count", 0)
277
+ if updated_count > 0:
278
+ lines.append(f"āœ“ Updated {updated_count} agents")
279
+ if verbose and "updated" in result:
280
+ for agent in result["updated"]:
281
+ lines.append(f" - {agent.get('name', agent)}")
282
+
283
+ # Skipped agents
284
+ if result.get("skipped"):
285
+ lines.append(f"→ Skipped {len(result['skipped'])} up-to-date agents")
286
+ if verbose:
287
+ for agent in result["skipped"]:
288
+ lines.append(f" - {agent.get('name', agent)}")
289
+
290
+ # Errors
291
+ if result.get("errors"):
292
+ lines.append(f"\nāŒ Encountered {len(result['errors'])} errors:")
293
+ for error in result["errors"]:
294
+ lines.append(f" - {error}")
295
+
296
+ # Target directory
297
+ if "target_dir" in result:
298
+ lines.append(f"\nTarget directory: {result['target_dir']}")
299
+
300
+ if not lines:
301
+ lines.append("No agents were deployed (all up to date)")
302
+
303
+ return "\n".join(lines)
304
+
305
+ def format_cleanup_result(
306
+ self, result: Dict[str, Any], output_format: str = "text", dry_run: bool = False
307
+ ) -> str:
308
+ """Format cleanup results.
309
+
310
+ Args:
311
+ result: Cleanup result dictionary
312
+ output_format: Output format (text, json, yaml)
313
+ dry_run: Whether this was a dry run
314
+
315
+ Returns:
316
+ Formatted string for display
317
+ """
318
+ if output_format == "json":
319
+ result["dry_run"] = dry_run
320
+ return self.format_as_json(result)
321
+ if output_format == "yaml":
322
+ result["dry_run"] = dry_run
323
+ return self.format_as_yaml(result)
324
+ # text format
325
+ lines = []
326
+
327
+ # Orphaned agents found
328
+ if result.get("orphaned"):
329
+ lines.append(f"Found {len(result['orphaned'])} orphaned agent(s):")
330
+ for orphan in result["orphaned"]:
331
+ name = orphan.get("name", "Unknown")
332
+ version = orphan.get("version", "Unknown")
333
+ lines.append(f" - {name} v{version}")
334
+
335
+ # Dry run vs actual cleanup
336
+ if dry_run:
337
+ if result.get("orphaned"):
338
+ lines.append(
339
+ f"\nšŸ“ This was a dry run. Use --force to actually remove "
340
+ f"{len(result['orphaned'])} orphaned agent(s)"
341
+ )
342
+ else:
343
+ lines.append("āœ… No orphaned agents found")
344
+ else:
345
+ # Removed agents
346
+ if result.get("removed"):
347
+ lines.append(
348
+ f"\nāœ… Successfully removed {len(result['removed'])} orphaned agent(s)"
349
+ )
350
+ for agent in result["removed"]:
351
+ lines.append(f" - {agent}")
352
+ elif "cleaned_count" in result:
353
+ cleaned_count = result["cleaned_count"]
354
+ if cleaned_count > 0:
355
+ lines.append(f"āœ“ Cleaned {cleaned_count} deployed agents")
356
+ else:
357
+ lines.append("No deployed agents to clean")
358
+ else:
359
+ lines.append("āœ… No orphaned agents found")
360
+
361
+ # Errors
362
+ if result.get("errors"):
363
+ lines.append(f"\nāŒ Encountered {len(result['errors'])} error(s):")
364
+ for error in result["errors"]:
365
+ lines.append(f" - {error}")
366
+
367
+ return "\n".join(lines)
368
+
369
+ def format_as_json(self, data: Any, pretty: bool = True) -> str:
370
+ """Format data as JSON.
371
+
372
+ Args:
373
+ data: Data to format
374
+ pretty: Use pretty printing with indentation
375
+
376
+ Returns:
377
+ JSON string
378
+ """
379
+ if pretty:
380
+ return json.dumps(data, indent=2, sort_keys=True)
381
+ return json.dumps(data)
382
+
383
+ def format_as_yaml(self, data: Any) -> str:
384
+ """Format data as YAML.
385
+
386
+ Args:
387
+ data: Data to format
388
+
389
+ Returns:
390
+ YAML string
391
+ """
392
+ return yaml.dump(data, default_flow_style=False, sort_keys=True)
393
+
394
+ def format_as_table(
395
+ self, headers: List[str], rows: List[List[str]], min_column_width: int = 10
396
+ ) -> str:
397
+ """Format data as table.
398
+
399
+ Args:
400
+ headers: Table headers
401
+ rows: Table rows
402
+ min_column_width: Minimum column width
403
+
404
+ Returns:
405
+ Formatted table string
406
+ """
407
+ # Calculate column widths
408
+ col_widths = [max(min_column_width, len(h)) for h in headers]
409
+ for row in rows:
410
+ for i, cell in enumerate(row):
411
+ if i < len(col_widths):
412
+ col_widths[i] = max(col_widths[i], len(str(cell)))
413
+
414
+ # Build table
415
+ lines = []
416
+
417
+ # Header
418
+ header_line = " | ".join(h.ljust(col_widths[i]) for i, h in enumerate(headers))
419
+ lines.append(header_line)
420
+ lines.append("-" * len(header_line))
421
+
422
+ # Rows
423
+ for row in rows:
424
+ row_line = " | ".join(
425
+ str(cell).ljust(col_widths[i]) if i < len(row) else " " * col_widths[i]
426
+ for i in range(len(headers))
427
+ for cell in [row[i] if i < len(row) else ""]
428
+ )
429
+ lines.append(row_line)
430
+
431
+ return "\n".join(lines)
432
+
433
+ def _format_agents_as_text(
434
+ self, agents: List[Dict[str, Any]], verbose: bool, quiet: bool
435
+ ) -> str:
436
+ """Format agents as text output.
437
+
438
+ Args:
439
+ agents: List of agent dictionaries
440
+ verbose: Include extra details
441
+ quiet: Minimal output
442
+
443
+ Returns:
444
+ Formatted text string
445
+ """
446
+ if not agents:
447
+ return "No agents found"
448
+
449
+ lines = []
450
+
451
+ if not quiet:
452
+ lines.append("Available Agents:")
453
+ lines.append("-" * 80)
454
+
455
+ for agent in agents:
456
+ if quiet:
457
+ # Minimal output - just names
458
+ lines.append(agent.get("name", agent.get("file", "Unknown")))
459
+ else:
460
+ # Standard output
461
+ lines.append(f"šŸ“„ {agent.get('file', 'Unknown')}")
462
+ if "name" in agent:
463
+ lines.append(f" Name: {agent['name']}")
464
+ if "description" in agent:
465
+ lines.append(f" Description: {agent['description']}")
466
+ if "version" in agent:
467
+ lines.append(f" Version: {agent['version']}")
468
+
469
+ # Verbose additions
470
+ if verbose:
471
+ if "path" in agent:
472
+ lines.append(f" Path: {agent['path']}")
473
+ if "tier" in agent:
474
+ lines.append(f" Tier: {agent['tier']}")
475
+ if agent.get("specializations"):
476
+ lines.append(
477
+ f" Specializations: {', '.join(agent['specializations'])}"
478
+ )
479
+
480
+ if not quiet:
481
+ lines.append("") # Empty line between agents
482
+
483
+ return "\n".join(lines)
484
+
485
+ def _format_agents_as_table(
486
+ self, agents: List[Dict[str, Any]], verbose: bool, quiet: bool
487
+ ) -> str:
488
+ """Format agents as table output.
489
+
490
+ Args:
491
+ agents: List of agent dictionaries
492
+ verbose: Include extra details
493
+ quiet: Minimal output
494
+
495
+ Returns:
496
+ Formatted table string
497
+ """
498
+ if not agents:
499
+ return "No agents found"
500
+
501
+ # Define headers based on verbosity
502
+ if quiet:
503
+ headers = ["Name"]
504
+ rows = [
505
+ [agent.get("name", agent.get("file", "Unknown"))] for agent in agents
506
+ ]
507
+ elif verbose:
508
+ headers = ["Name", "Version", "Tier", "Description", "Path"]
509
+ rows = []
510
+ for agent in agents:
511
+ rows.append(
512
+ [
513
+ agent.get("name", agent.get("file", "Unknown")),
514
+ agent.get("version", "-"),
515
+ agent.get("tier", "-"),
516
+ agent.get("description", "-")[
517
+ :50
518
+ ], # Truncate long descriptions
519
+ str(agent.get("path", "-"))[:40], # Truncate long paths
520
+ ]
521
+ )
522
+ else:
523
+ headers = ["Name", "Version", "Description"]
524
+ rows = []
525
+ for agent in agents:
526
+ rows.append(
527
+ [
528
+ agent.get("name", agent.get("file", "Unknown")),
529
+ agent.get("version", "-"),
530
+ agent.get("description", "-")[
531
+ :60
532
+ ], # Truncate long descriptions
533
+ ]
534
+ )
535
+
536
+ return self.format_as_table(headers, rows)
537
+
538
+ def format_agents_by_tier(
539
+ self, agents_by_tier: Dict[str, List[str]], output_format: str = "text"
540
+ ) -> str:
541
+ """Format agents grouped by tier.
542
+
543
+ Args:
544
+ agents_by_tier: Dictionary mapping tier names to agent lists
545
+ output_format: Output format (text, json, yaml)
546
+
547
+ Returns:
548
+ Formatted string for display
549
+ """
550
+ if output_format == "json":
551
+ return self.format_as_json(agents_by_tier)
552
+ if output_format == "yaml":
553
+ return self.format_as_yaml(agents_by_tier)
554
+ # text format
555
+ lines = []
556
+ lines.append("Agents by Tier/Precedence:")
557
+ lines.append("=" * 50)
558
+
559
+ for tier, agents in agents_by_tier.items():
560
+ lines.append(f"\n{tier.upper()}:")
561
+ lines.append("-" * 20)
562
+ if agents:
563
+ for agent in agents:
564
+ lines.append(f" • {agent}")
565
+ else:
566
+ lines.append(" (none)")
567
+
568
+ return "\n".join(lines)
569
+
570
+ def format_fix_result(
571
+ self, result: Dict[str, Any], output_format: str = "text"
572
+ ) -> str:
573
+ """Format fix operation results.
574
+
575
+ Args:
576
+ result: Fix operation result dictionary
577
+ output_format: Output format (text, json, yaml)
578
+
579
+ Returns:
580
+ Formatted string for display
581
+ """
582
+ if output_format == "json":
583
+ return self.format_as_json(result)
584
+ if output_format == "yaml":
585
+ return self.format_as_yaml(result)
586
+ # text format
587
+ lines = []
588
+ lines.append("āœ“ Agent deployment issues fixed")
589
+
590
+ if result.get("fixes_applied"):
591
+ lines.append("\nFixes applied:")
592
+ for fix in result["fixes_applied"]:
593
+ lines.append(f" - {fix}")
594
+
595
+ if result.get("errors"):
596
+ lines.append(f"\nāŒ Encountered {len(result['errors'])} error(s):")
597
+ for error in result["errors"]:
598
+ lines.append(f" - {error}")
599
+
600
+ if result.get("warnings"):
601
+ lines.append("\nāš ļø Warnings:")
602
+ for warning in result["warnings"]:
603
+ lines.append(f" - {warning}")
604
+
605
+ return "\n".join(lines)