claude-mpm 3.9.11__py3-none-any.whl → 4.0.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 (419) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/__init__.py +2 -2
  3. claude_mpm/__main__.py +3 -2
  4. claude_mpm/agents/__init__.py +85 -79
  5. claude_mpm/agents/agent_loader.py +464 -1003
  6. claude_mpm/agents/agent_loader_integration.py +45 -45
  7. claude_mpm/agents/agents_metadata.py +29 -30
  8. claude_mpm/agents/async_agent_loader.py +156 -138
  9. claude_mpm/agents/base_agent.json +1 -1
  10. claude_mpm/agents/base_agent_loader.py +179 -151
  11. claude_mpm/agents/frontmatter_validator.py +229 -130
  12. claude_mpm/agents/schema/agent_schema.json +1 -1
  13. claude_mpm/agents/system_agent_config.py +213 -147
  14. claude_mpm/agents/templates/__init__.py +13 -13
  15. claude_mpm/agents/templates/code_analyzer.json +2 -2
  16. claude_mpm/agents/templates/data_engineer.json +1 -1
  17. claude_mpm/agents/templates/documentation.json +23 -11
  18. claude_mpm/agents/templates/engineer.json +22 -6
  19. claude_mpm/agents/templates/memory_manager.json +1 -1
  20. claude_mpm/agents/templates/ops.json +2 -2
  21. claude_mpm/agents/templates/project_organizer.json +1 -1
  22. claude_mpm/agents/templates/qa.json +1 -1
  23. claude_mpm/agents/templates/refactoring_engineer.json +222 -0
  24. claude_mpm/agents/templates/research.json +20 -14
  25. claude_mpm/agents/templates/security.json +1 -1
  26. claude_mpm/agents/templates/ticketing.json +1 -1
  27. claude_mpm/agents/templates/version_control.json +1 -1
  28. claude_mpm/agents/templates/web_qa.json +3 -1
  29. claude_mpm/agents/templates/web_ui.json +2 -2
  30. claude_mpm/cli/__init__.py +79 -51
  31. claude_mpm/cli/__main__.py +3 -2
  32. claude_mpm/cli/commands/__init__.py +20 -20
  33. claude_mpm/cli/commands/agents.py +279 -247
  34. claude_mpm/cli/commands/aggregate.py +138 -157
  35. claude_mpm/cli/commands/cleanup.py +147 -147
  36. claude_mpm/cli/commands/config.py +93 -76
  37. claude_mpm/cli/commands/info.py +17 -16
  38. claude_mpm/cli/commands/mcp.py +140 -905
  39. claude_mpm/cli/commands/mcp_command_router.py +139 -0
  40. claude_mpm/cli/commands/mcp_config_commands.py +20 -0
  41. claude_mpm/cli/commands/mcp_install_commands.py +20 -0
  42. claude_mpm/cli/commands/mcp_server_commands.py +175 -0
  43. claude_mpm/cli/commands/mcp_tool_commands.py +34 -0
  44. claude_mpm/cli/commands/memory.py +239 -203
  45. claude_mpm/cli/commands/monitor.py +203 -81
  46. claude_mpm/cli/commands/run.py +380 -429
  47. claude_mpm/cli/commands/run_config_checker.py +160 -0
  48. claude_mpm/cli/commands/socketio_monitor.py +235 -0
  49. claude_mpm/cli/commands/tickets.py +305 -197
  50. claude_mpm/cli/parser.py +24 -1156
  51. claude_mpm/cli/parsers/__init__.py +29 -0
  52. claude_mpm/cli/parsers/agents_parser.py +136 -0
  53. claude_mpm/cli/parsers/base_parser.py +331 -0
  54. claude_mpm/cli/parsers/config_parser.py +85 -0
  55. claude_mpm/cli/parsers/mcp_parser.py +152 -0
  56. claude_mpm/cli/parsers/memory_parser.py +138 -0
  57. claude_mpm/cli/parsers/monitor_parser.py +104 -0
  58. claude_mpm/cli/parsers/run_parser.py +147 -0
  59. claude_mpm/cli/parsers/tickets_parser.py +203 -0
  60. claude_mpm/cli/ticket_cli.py +7 -3
  61. claude_mpm/cli/utils.py +55 -37
  62. claude_mpm/cli_module/__init__.py +6 -6
  63. claude_mpm/cli_module/args.py +188 -140
  64. claude_mpm/cli_module/commands.py +79 -70
  65. claude_mpm/cli_module/migration_example.py +38 -60
  66. claude_mpm/config/__init__.py +32 -25
  67. claude_mpm/config/agent_config.py +151 -119
  68. claude_mpm/config/experimental_features.py +71 -73
  69. claude_mpm/config/paths.py +94 -208
  70. claude_mpm/config/socketio_config.py +84 -73
  71. claude_mpm/constants.py +35 -18
  72. claude_mpm/core/__init__.py +9 -6
  73. claude_mpm/core/agent_name_normalizer.py +68 -71
  74. claude_mpm/core/agent_registry.py +372 -521
  75. claude_mpm/core/agent_session_manager.py +74 -63
  76. claude_mpm/core/base_service.py +116 -87
  77. claude_mpm/core/cache.py +119 -153
  78. claude_mpm/core/claude_runner.py +425 -1120
  79. claude_mpm/core/config.py +263 -168
  80. claude_mpm/core/config_aliases.py +69 -61
  81. claude_mpm/core/config_constants.py +292 -0
  82. claude_mpm/core/constants.py +57 -99
  83. claude_mpm/core/container.py +211 -178
  84. claude_mpm/core/exceptions.py +233 -89
  85. claude_mpm/core/factories.py +92 -54
  86. claude_mpm/core/framework_loader.py +378 -220
  87. claude_mpm/core/hook_manager.py +198 -83
  88. claude_mpm/core/hook_performance_config.py +136 -0
  89. claude_mpm/core/injectable_service.py +61 -55
  90. claude_mpm/core/interactive_session.py +165 -155
  91. claude_mpm/core/interfaces.py +221 -195
  92. claude_mpm/core/lazy.py +96 -96
  93. claude_mpm/core/logger.py +133 -107
  94. claude_mpm/core/logging_config.py +185 -157
  95. claude_mpm/core/minimal_framework_loader.py +20 -15
  96. claude_mpm/core/mixins.py +30 -29
  97. claude_mpm/core/oneshot_session.py +215 -181
  98. claude_mpm/core/optimized_agent_loader.py +134 -138
  99. claude_mpm/core/optimized_startup.py +159 -157
  100. claude_mpm/core/pm_hook_interceptor.py +85 -72
  101. claude_mpm/core/service_registry.py +103 -101
  102. claude_mpm/core/session_manager.py +97 -87
  103. claude_mpm/core/socketio_pool.py +212 -158
  104. claude_mpm/core/tool_access_control.py +58 -51
  105. claude_mpm/core/types.py +46 -24
  106. claude_mpm/core/typing_utils.py +166 -82
  107. claude_mpm/core/unified_agent_registry.py +721 -0
  108. claude_mpm/core/unified_config.py +550 -0
  109. claude_mpm/core/unified_paths.py +549 -0
  110. claude_mpm/dashboard/index.html +1 -1
  111. claude_mpm/dashboard/open_dashboard.py +51 -17
  112. claude_mpm/dashboard/static/css/dashboard.css +27 -8
  113. claude_mpm/dashboard/static/dist/components/agent-inference.js +2 -0
  114. claude_mpm/dashboard/static/dist/components/event-processor.js +2 -0
  115. claude_mpm/dashboard/static/dist/components/event-viewer.js +2 -0
  116. claude_mpm/dashboard/static/dist/components/export-manager.js +2 -0
  117. claude_mpm/dashboard/static/dist/components/file-tool-tracker.js +2 -0
  118. claude_mpm/dashboard/static/dist/components/hud-library-loader.js +2 -0
  119. claude_mpm/dashboard/static/dist/components/hud-manager.js +2 -0
  120. claude_mpm/dashboard/static/dist/components/hud-visualizer.js +2 -0
  121. claude_mpm/dashboard/static/dist/components/module-viewer.js +2 -0
  122. claude_mpm/dashboard/static/dist/components/session-manager.js +2 -0
  123. claude_mpm/dashboard/static/dist/components/socket-manager.js +2 -0
  124. claude_mpm/dashboard/static/dist/components/ui-state-manager.js +2 -0
  125. claude_mpm/dashboard/static/dist/components/working-directory.js +2 -0
  126. claude_mpm/dashboard/static/dist/dashboard.js +2 -0
  127. claude_mpm/dashboard/static/dist/socket-client.js +2 -0
  128. claude_mpm/dashboard/static/js/components/agent-inference.js +80 -76
  129. claude_mpm/dashboard/static/js/components/event-processor.js +71 -67
  130. claude_mpm/dashboard/static/js/components/event-viewer.js +74 -70
  131. claude_mpm/dashboard/static/js/components/export-manager.js +31 -28
  132. claude_mpm/dashboard/static/js/components/file-tool-tracker.js +106 -92
  133. claude_mpm/dashboard/static/js/components/hud-library-loader.js +11 -11
  134. claude_mpm/dashboard/static/js/components/hud-manager.js +73 -73
  135. claude_mpm/dashboard/static/js/components/hud-visualizer.js +163 -163
  136. claude_mpm/dashboard/static/js/components/module-viewer.js +305 -233
  137. claude_mpm/dashboard/static/js/components/session-manager.js +32 -29
  138. claude_mpm/dashboard/static/js/components/socket-manager.js +27 -20
  139. claude_mpm/dashboard/static/js/components/ui-state-manager.js +21 -18
  140. claude_mpm/dashboard/static/js/components/working-directory.js +74 -71
  141. claude_mpm/dashboard/static/js/dashboard.js +178 -453
  142. claude_mpm/dashboard/static/js/extension-error-handler.js +164 -0
  143. claude_mpm/dashboard/static/js/socket-client.js +120 -54
  144. claude_mpm/dashboard/templates/index.html +40 -50
  145. claude_mpm/experimental/cli_enhancements.py +60 -58
  146. claude_mpm/generators/__init__.py +1 -1
  147. claude_mpm/generators/agent_profile_generator.py +75 -65
  148. claude_mpm/hooks/__init__.py +1 -1
  149. claude_mpm/hooks/base_hook.py +33 -28
  150. claude_mpm/hooks/claude_hooks/__init__.py +1 -1
  151. claude_mpm/hooks/claude_hooks/connection_pool.py +120 -0
  152. claude_mpm/hooks/claude_hooks/event_handlers.py +743 -0
  153. claude_mpm/hooks/claude_hooks/hook_handler.py +415 -1331
  154. claude_mpm/hooks/claude_hooks/hook_wrapper.sh +4 -4
  155. claude_mpm/hooks/claude_hooks/memory_integration.py +221 -0
  156. claude_mpm/hooks/claude_hooks/response_tracking.py +348 -0
  157. claude_mpm/hooks/claude_hooks/tool_analysis.py +230 -0
  158. claude_mpm/hooks/memory_integration_hook.py +140 -100
  159. claude_mpm/hooks/tool_call_interceptor.py +89 -76
  160. claude_mpm/hooks/validation_hooks.py +57 -49
  161. claude_mpm/init.py +145 -121
  162. claude_mpm/models/__init__.py +9 -9
  163. claude_mpm/models/agent_definition.py +33 -23
  164. claude_mpm/models/agent_session.py +228 -200
  165. claude_mpm/scripts/__init__.py +1 -1
  166. claude_mpm/scripts/socketio_daemon.py +192 -75
  167. claude_mpm/scripts/socketio_server_manager.py +328 -0
  168. claude_mpm/scripts/start_activity_logging.py +25 -22
  169. claude_mpm/services/__init__.py +68 -43
  170. claude_mpm/services/agent_capabilities_service.py +271 -0
  171. claude_mpm/services/agents/__init__.py +23 -32
  172. claude_mpm/services/agents/deployment/__init__.py +3 -3
  173. claude_mpm/services/agents/deployment/agent_config_provider.py +310 -0
  174. claude_mpm/services/agents/deployment/agent_configuration_manager.py +359 -0
  175. claude_mpm/services/agents/deployment/agent_definition_factory.py +84 -0
  176. claude_mpm/services/agents/deployment/agent_deployment.py +415 -2113
  177. claude_mpm/services/agents/deployment/agent_discovery_service.py +387 -0
  178. claude_mpm/services/agents/deployment/agent_environment_manager.py +293 -0
  179. claude_mpm/services/agents/deployment/agent_filesystem_manager.py +387 -0
  180. claude_mpm/services/agents/deployment/agent_format_converter.py +453 -0
  181. claude_mpm/services/agents/deployment/agent_frontmatter_validator.py +161 -0
  182. claude_mpm/services/agents/deployment/agent_lifecycle_manager.py +345 -495
  183. claude_mpm/services/agents/deployment/agent_metrics_collector.py +279 -0
  184. claude_mpm/services/agents/deployment/agent_restore_handler.py +88 -0
  185. claude_mpm/services/agents/deployment/agent_template_builder.py +406 -0
  186. claude_mpm/services/agents/deployment/agent_validator.py +352 -0
  187. claude_mpm/services/agents/deployment/agent_version_manager.py +313 -0
  188. claude_mpm/services/agents/deployment/agent_versioning.py +6 -9
  189. claude_mpm/services/agents/deployment/agents_directory_resolver.py +79 -0
  190. claude_mpm/services/agents/deployment/async_agent_deployment.py +298 -234
  191. claude_mpm/services/agents/deployment/config/__init__.py +13 -0
  192. claude_mpm/services/agents/deployment/config/deployment_config.py +182 -0
  193. claude_mpm/services/agents/deployment/config/deployment_config_manager.py +200 -0
  194. claude_mpm/services/agents/deployment/deployment_config_loader.py +54 -0
  195. claude_mpm/services/agents/deployment/deployment_type_detector.py +124 -0
  196. claude_mpm/services/agents/deployment/facade/__init__.py +18 -0
  197. claude_mpm/services/agents/deployment/facade/async_deployment_executor.py +159 -0
  198. claude_mpm/services/agents/deployment/facade/deployment_executor.py +73 -0
  199. claude_mpm/services/agents/deployment/facade/deployment_facade.py +270 -0
  200. claude_mpm/services/agents/deployment/facade/sync_deployment_executor.py +178 -0
  201. claude_mpm/services/agents/deployment/interface_adapter.py +227 -0
  202. claude_mpm/services/agents/deployment/lifecycle_health_checker.py +85 -0
  203. claude_mpm/services/agents/deployment/lifecycle_performance_tracker.py +100 -0
  204. claude_mpm/services/agents/deployment/pipeline/__init__.py +32 -0
  205. claude_mpm/services/agents/deployment/pipeline/pipeline_builder.py +158 -0
  206. claude_mpm/services/agents/deployment/pipeline/pipeline_context.py +159 -0
  207. claude_mpm/services/agents/deployment/pipeline/pipeline_executor.py +169 -0
  208. claude_mpm/services/agents/deployment/pipeline/steps/__init__.py +19 -0
  209. claude_mpm/services/agents/deployment/pipeline/steps/agent_processing_step.py +195 -0
  210. claude_mpm/services/agents/deployment/pipeline/steps/base_step.py +119 -0
  211. claude_mpm/services/agents/deployment/pipeline/steps/configuration_step.py +79 -0
  212. claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +90 -0
  213. claude_mpm/services/agents/deployment/pipeline/steps/validation_step.py +100 -0
  214. claude_mpm/services/agents/deployment/processors/__init__.py +15 -0
  215. claude_mpm/services/agents/deployment/processors/agent_deployment_context.py +98 -0
  216. claude_mpm/services/agents/deployment/processors/agent_deployment_result.py +235 -0
  217. claude_mpm/services/agents/deployment/processors/agent_processor.py +258 -0
  218. claude_mpm/services/agents/deployment/refactored_agent_deployment_service.py +318 -0
  219. claude_mpm/services/agents/deployment/results/__init__.py +13 -0
  220. claude_mpm/services/agents/deployment/results/deployment_metrics.py +200 -0
  221. claude_mpm/services/agents/deployment/results/deployment_result_builder.py +249 -0
  222. claude_mpm/services/agents/deployment/strategies/__init__.py +25 -0
  223. claude_mpm/services/agents/deployment/strategies/base_strategy.py +119 -0
  224. claude_mpm/services/agents/deployment/strategies/project_strategy.py +150 -0
  225. claude_mpm/services/agents/deployment/strategies/strategy_selector.py +117 -0
  226. claude_mpm/services/agents/deployment/strategies/system_strategy.py +116 -0
  227. claude_mpm/services/agents/deployment/strategies/user_strategy.py +137 -0
  228. claude_mpm/services/agents/deployment/system_instructions_deployer.py +108 -0
  229. claude_mpm/services/agents/deployment/validation/__init__.py +19 -0
  230. claude_mpm/services/agents/deployment/validation/agent_validator.py +323 -0
  231. claude_mpm/services/agents/deployment/validation/deployment_validator.py +238 -0
  232. claude_mpm/services/agents/deployment/validation/template_validator.py +299 -0
  233. claude_mpm/services/agents/deployment/validation/validation_result.py +226 -0
  234. claude_mpm/services/agents/loading/__init__.py +2 -2
  235. claude_mpm/services/agents/loading/agent_profile_loader.py +259 -229
  236. claude_mpm/services/agents/loading/base_agent_manager.py +90 -81
  237. claude_mpm/services/agents/loading/framework_agent_loader.py +154 -129
  238. claude_mpm/services/agents/management/__init__.py +2 -2
  239. claude_mpm/services/agents/management/agent_capabilities_generator.py +72 -58
  240. claude_mpm/services/agents/management/agent_management_service.py +209 -156
  241. claude_mpm/services/agents/memory/__init__.py +9 -6
  242. claude_mpm/services/agents/memory/agent_memory_manager.py +218 -1152
  243. claude_mpm/services/agents/memory/agent_persistence_service.py +20 -16
  244. claude_mpm/services/agents/memory/analyzer.py +430 -0
  245. claude_mpm/services/agents/memory/content_manager.py +376 -0
  246. claude_mpm/services/agents/memory/template_generator.py +468 -0
  247. claude_mpm/services/agents/registry/__init__.py +7 -10
  248. claude_mpm/services/agents/registry/deployed_agent_discovery.py +122 -97
  249. claude_mpm/services/agents/registry/modification_tracker.py +351 -285
  250. claude_mpm/services/async_session_logger.py +187 -153
  251. claude_mpm/services/claude_session_logger.py +87 -72
  252. claude_mpm/services/command_handler_service.py +217 -0
  253. claude_mpm/services/communication/__init__.py +3 -2
  254. claude_mpm/services/core/__init__.py +50 -97
  255. claude_mpm/services/core/base.py +60 -53
  256. claude_mpm/services/core/interfaces/__init__.py +188 -0
  257. claude_mpm/services/core/interfaces/agent.py +351 -0
  258. claude_mpm/services/core/interfaces/communication.py +343 -0
  259. claude_mpm/services/core/interfaces/infrastructure.py +413 -0
  260. claude_mpm/services/core/interfaces/service.py +434 -0
  261. claude_mpm/services/core/interfaces.py +19 -944
  262. claude_mpm/services/event_aggregator.py +208 -170
  263. claude_mpm/services/exceptions.py +387 -308
  264. claude_mpm/services/framework_claude_md_generator/__init__.py +75 -79
  265. claude_mpm/services/framework_claude_md_generator/content_assembler.py +69 -60
  266. claude_mpm/services/framework_claude_md_generator/content_validator.py +65 -61
  267. claude_mpm/services/framework_claude_md_generator/deployment_manager.py +68 -49
  268. claude_mpm/services/framework_claude_md_generator/section_generators/__init__.py +34 -34
  269. claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +25 -22
  270. claude_mpm/services/framework_claude_md_generator/section_generators/claude_pm_init.py +10 -10
  271. claude_mpm/services/framework_claude_md_generator/section_generators/core_responsibilities.py +4 -3
  272. claude_mpm/services/framework_claude_md_generator/section_generators/delegation_constraints.py +4 -3
  273. claude_mpm/services/framework_claude_md_generator/section_generators/environment_config.py +4 -3
  274. claude_mpm/services/framework_claude_md_generator/section_generators/footer.py +6 -5
  275. claude_mpm/services/framework_claude_md_generator/section_generators/header.py +8 -7
  276. claude_mpm/services/framework_claude_md_generator/section_generators/orchestration_principles.py +4 -3
  277. claude_mpm/services/framework_claude_md_generator/section_generators/role_designation.py +6 -5
  278. claude_mpm/services/framework_claude_md_generator/section_generators/subprocess_validation.py +9 -8
  279. claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +4 -3
  280. claude_mpm/services/framework_claude_md_generator/section_generators/troubleshooting.py +5 -4
  281. claude_mpm/services/framework_claude_md_generator/section_manager.py +28 -27
  282. claude_mpm/services/framework_claude_md_generator/version_manager.py +30 -28
  283. claude_mpm/services/hook_service.py +106 -114
  284. claude_mpm/services/infrastructure/__init__.py +7 -5
  285. claude_mpm/services/infrastructure/context_preservation.py +233 -199
  286. claude_mpm/services/infrastructure/daemon_manager.py +279 -0
  287. claude_mpm/services/infrastructure/logging.py +83 -76
  288. claude_mpm/services/infrastructure/monitoring.py +547 -404
  289. claude_mpm/services/mcp_gateway/__init__.py +30 -13
  290. claude_mpm/services/mcp_gateway/config/__init__.py +2 -2
  291. claude_mpm/services/mcp_gateway/config/config_loader.py +61 -56
  292. claude_mpm/services/mcp_gateway/config/config_schema.py +50 -41
  293. claude_mpm/services/mcp_gateway/config/configuration.py +82 -75
  294. claude_mpm/services/mcp_gateway/core/__init__.py +13 -20
  295. claude_mpm/services/mcp_gateway/core/base.py +80 -67
  296. claude_mpm/services/mcp_gateway/core/exceptions.py +60 -46
  297. claude_mpm/services/mcp_gateway/core/interfaces.py +87 -84
  298. claude_mpm/services/mcp_gateway/main.py +287 -137
  299. claude_mpm/services/mcp_gateway/registry/__init__.py +1 -1
  300. claude_mpm/services/mcp_gateway/registry/service_registry.py +97 -94
  301. claude_mpm/services/mcp_gateway/registry/tool_registry.py +135 -126
  302. claude_mpm/services/mcp_gateway/server/__init__.py +2 -2
  303. claude_mpm/services/mcp_gateway/server/mcp_gateway.py +105 -110
  304. claude_mpm/services/mcp_gateway/server/stdio_handler.py +105 -107
  305. claude_mpm/services/mcp_gateway/server/stdio_server.py +691 -0
  306. claude_mpm/services/mcp_gateway/tools/__init__.py +4 -2
  307. claude_mpm/services/mcp_gateway/tools/base_adapter.py +109 -119
  308. claude_mpm/services/mcp_gateway/tools/document_summarizer.py +283 -215
  309. claude_mpm/services/mcp_gateway/tools/hello_world.py +122 -120
  310. claude_mpm/services/mcp_gateway/tools/ticket_tools.py +652 -0
  311. claude_mpm/services/mcp_gateway/tools/unified_ticket_tool.py +606 -0
  312. claude_mpm/services/memory/__init__.py +2 -2
  313. claude_mpm/services/memory/builder.py +451 -362
  314. claude_mpm/services/memory/cache/__init__.py +2 -2
  315. claude_mpm/services/memory/cache/shared_prompt_cache.py +232 -194
  316. claude_mpm/services/memory/cache/simple_cache.py +107 -93
  317. claude_mpm/services/memory/indexed_memory.py +195 -193
  318. claude_mpm/services/memory/optimizer.py +267 -234
  319. claude_mpm/services/memory/router.py +571 -263
  320. claude_mpm/services/memory_hook_service.py +237 -0
  321. claude_mpm/services/port_manager.py +223 -0
  322. claude_mpm/services/project/__init__.py +3 -3
  323. claude_mpm/services/project/analyzer.py +451 -305
  324. claude_mpm/services/project/registry.py +262 -240
  325. claude_mpm/services/recovery_manager.py +287 -231
  326. claude_mpm/services/response_tracker.py +87 -67
  327. claude_mpm/services/runner_configuration_service.py +587 -0
  328. claude_mpm/services/session_management_service.py +304 -0
  329. claude_mpm/services/socketio/__init__.py +4 -4
  330. claude_mpm/services/socketio/client_proxy.py +174 -0
  331. claude_mpm/services/socketio/handlers/__init__.py +3 -3
  332. claude_mpm/services/socketio/handlers/base.py +44 -30
  333. claude_mpm/services/socketio/handlers/connection.py +145 -65
  334. claude_mpm/services/socketio/handlers/file.py +123 -108
  335. claude_mpm/services/socketio/handlers/git.py +607 -373
  336. claude_mpm/services/socketio/handlers/hook.py +170 -0
  337. claude_mpm/services/socketio/handlers/memory.py +4 -4
  338. claude_mpm/services/socketio/handlers/project.py +4 -4
  339. claude_mpm/services/socketio/handlers/registry.py +53 -38
  340. claude_mpm/services/socketio/server/__init__.py +18 -0
  341. claude_mpm/services/socketio/server/broadcaster.py +252 -0
  342. claude_mpm/services/socketio/server/core.py +399 -0
  343. claude_mpm/services/socketio/server/main.py +323 -0
  344. claude_mpm/services/socketio_client_manager.py +160 -133
  345. claude_mpm/services/socketio_server.py +36 -1885
  346. claude_mpm/services/subprocess_launcher_service.py +316 -0
  347. claude_mpm/services/system_instructions_service.py +258 -0
  348. claude_mpm/services/ticket_manager.py +19 -533
  349. claude_mpm/services/utility_service.py +285 -0
  350. claude_mpm/services/version_control/__init__.py +18 -21
  351. claude_mpm/services/version_control/branch_strategy.py +20 -10
  352. claude_mpm/services/version_control/conflict_resolution.py +37 -13
  353. claude_mpm/services/version_control/git_operations.py +52 -21
  354. claude_mpm/services/version_control/semantic_versioning.py +92 -53
  355. claude_mpm/services/version_control/version_parser.py +145 -125
  356. claude_mpm/services/version_service.py +270 -0
  357. claude_mpm/storage/__init__.py +2 -2
  358. claude_mpm/storage/state_storage.py +177 -181
  359. claude_mpm/ticket_wrapper.py +2 -2
  360. claude_mpm/utils/__init__.py +2 -2
  361. claude_mpm/utils/agent_dependency_loader.py +453 -243
  362. claude_mpm/utils/config_manager.py +157 -118
  363. claude_mpm/utils/console.py +1 -1
  364. claude_mpm/utils/dependency_cache.py +102 -107
  365. claude_mpm/utils/dependency_manager.py +52 -47
  366. claude_mpm/utils/dependency_strategies.py +131 -96
  367. claude_mpm/utils/environment_context.py +110 -102
  368. claude_mpm/utils/error_handler.py +75 -55
  369. claude_mpm/utils/file_utils.py +80 -67
  370. claude_mpm/utils/framework_detection.py +12 -11
  371. claude_mpm/utils/import_migration_example.py +12 -60
  372. claude_mpm/utils/imports.py +48 -45
  373. claude_mpm/utils/path_operations.py +100 -93
  374. claude_mpm/utils/robust_installer.py +172 -164
  375. claude_mpm/utils/session_logging.py +30 -23
  376. claude_mpm/utils/subprocess_utils.py +99 -61
  377. claude_mpm/validation/__init__.py +1 -1
  378. claude_mpm/validation/agent_validator.py +151 -111
  379. claude_mpm/validation/frontmatter_validator.py +92 -71
  380. {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.3.dist-info}/METADATA +27 -1
  381. claude_mpm-4.0.3.dist-info/RECORD +402 -0
  382. {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.3.dist-info}/entry_points.txt +1 -0
  383. {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.3.dist-info}/licenses/LICENSE +1 -1
  384. claude_mpm/cli/commands/run_guarded.py +0 -511
  385. claude_mpm/config/memory_guardian_config.py +0 -325
  386. claude_mpm/config/memory_guardian_yaml.py +0 -335
  387. claude_mpm/core/config_paths.py +0 -150
  388. claude_mpm/core/memory_aware_runner.py +0 -353
  389. claude_mpm/dashboard/static/js/dashboard-original.js +0 -4134
  390. claude_mpm/deployment_paths.py +0 -261
  391. claude_mpm/hooks/claude_hooks/hook_handler_fixed.py +0 -454
  392. claude_mpm/models/state_models.py +0 -433
  393. claude_mpm/services/agent/__init__.py +0 -24
  394. claude_mpm/services/agent/deployment.py +0 -2548
  395. claude_mpm/services/agent/management.py +0 -598
  396. claude_mpm/services/agent/registry.py +0 -813
  397. claude_mpm/services/agents/registry/agent_registry.py +0 -813
  398. claude_mpm/services/communication/socketio.py +0 -1935
  399. claude_mpm/services/communication/websocket.py +0 -479
  400. claude_mpm/services/framework_claude_md_generator.py +0 -624
  401. claude_mpm/services/health_monitor.py +0 -893
  402. claude_mpm/services/infrastructure/graceful_degradation.py +0 -616
  403. claude_mpm/services/infrastructure/health_monitor.py +0 -775
  404. claude_mpm/services/infrastructure/memory_dashboard.py +0 -479
  405. claude_mpm/services/infrastructure/memory_guardian.py +0 -944
  406. claude_mpm/services/infrastructure/restart_protection.py +0 -642
  407. claude_mpm/services/infrastructure/state_manager.py +0 -774
  408. claude_mpm/services/mcp_gateway/manager.py +0 -334
  409. claude_mpm/services/optimized_hook_service.py +0 -542
  410. claude_mpm/services/project_analyzer.py +0 -864
  411. claude_mpm/services/project_registry.py +0 -608
  412. claude_mpm/services/standalone_socketio_server.py +0 -1300
  413. claude_mpm/services/ticket_manager_di.py +0 -318
  414. claude_mpm/services/ticketing_service_original.py +0 -510
  415. claude_mpm/utils/paths.py +0 -395
  416. claude_mpm/utils/platform_memory.py +0 -524
  417. claude_mpm-3.9.11.dist-info/RECORD +0 -306
  418. {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.3.dist-info}/WHEEL +0 -0
  419. {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.3.dist-info}/top_level.txt +0 -0
@@ -27,40 +27,50 @@ ROLLBACK PROCEDURES:
27
27
  - Version tracking allows targeted rollbacks
28
28
  """
29
29
 
30
+ import logging
30
31
  import os
31
32
  import shutil
32
- import logging
33
33
  import time
34
34
  from pathlib import Path
35
- from typing import Optional, List, Dict, Any
35
+ from typing import Any, Dict, List, Optional
36
36
 
37
- from claude_mpm.core.logging_config import get_logger, log_operation, log_performance_context
38
- from claude_mpm.core.exceptions import AgentDeploymentError
39
- from claude_mpm.constants import EnvironmentVars, Paths, AgentMetadata
40
37
  from claude_mpm.config.paths import paths
38
+ from claude_mpm.constants import AgentMetadata, EnvironmentVars, Paths
41
39
  from claude_mpm.core.config import Config
42
- from claude_mpm.core.constants import (
43
- TimeoutConfig,
44
- SystemLimits,
45
- ResourceLimits
46
- )
40
+ from claude_mpm.core.constants import ResourceLimits, SystemLimits, TimeoutConfig
41
+ from claude_mpm.core.exceptions import AgentDeploymentError
47
42
  from claude_mpm.core.interfaces import AgentDeploymentInterface
43
+ from claude_mpm.core.logging_config import (
44
+ get_logger,
45
+ log_operation,
46
+ log_performance_context,
47
+ )
48
+
49
+ from .agent_configuration_manager import AgentConfigurationManager
50
+ from .agent_discovery_service import AgentDiscoveryService
51
+ from .agent_environment_manager import AgentEnvironmentManager
52
+ from .agent_filesystem_manager import AgentFileSystemManager
53
+ from .agent_format_converter import AgentFormatConverter
54
+ from .agent_metrics_collector import AgentMetricsCollector
55
+ from .agent_template_builder import AgentTemplateBuilder
56
+ from .agent_validator import AgentValidator
57
+ from .agent_version_manager import AgentVersionManager
48
58
 
49
59
 
50
60
  class AgentDeploymentService(AgentDeploymentInterface):
51
61
  """Service for deploying Claude Code native agents.
52
-
62
+
53
63
  METRICS COLLECTION OPPORTUNITIES:
54
64
  This service could collect valuable deployment metrics including:
55
65
  - Agent deployment frequency and success rates
56
- - Template validation performance
66
+ - Template validation performance
57
67
  - Version migration patterns
58
68
  - Deployment duration by agent type
59
69
  - Cache hit rates for agent templates
60
70
  - Resource usage during deployment (memory, CPU)
61
71
  - Agent file sizes and complexity metrics
62
72
  - Deployment failure reasons and patterns
63
-
73
+
64
74
  DEPLOYMENT PIPELINE:
65
75
  1. Initialize with template and base agent paths
66
76
  2. Load base agent configuration (shared settings)
@@ -70,45 +80,64 @@ class AgentDeploymentService(AgentDeploymentInterface):
70
80
  6. Deploy to target directory
71
81
  7. Set environment variables for discovery
72
82
  8. Verify deployment success
73
-
83
+
74
84
  ENVIRONMENT REQUIREMENTS:
75
85
  - Write access to .claude/agents directory
76
86
  - Python 3.8+ for pathlib and typing features
77
87
  - JSON parsing for template files
78
88
  - YAML generation capabilities
79
89
  """
80
-
81
- def __init__(self, templates_dir: Optional[Path] = None, base_agent_path: Optional[Path] = None, working_directory: Optional[Path] = None):
90
+
91
+ def __init__(
92
+ self,
93
+ templates_dir: Optional[Path] = None,
94
+ base_agent_path: Optional[Path] = None,
95
+ working_directory: Optional[Path] = None,
96
+ ):
82
97
  """
83
98
  Initialize agent deployment service.
84
-
99
+
85
100
  Args:
86
101
  templates_dir: Directory containing agent JSON files
87
102
  base_agent_path: Path to base_agent.md file
88
103
  working_directory: User's working directory (for project agents)
89
-
104
+
90
105
  METRICS OPPORTUNITY: Track initialization performance:
91
106
  - Template directory scan time
92
107
  - Base agent loading time
93
108
  - Initial validation overhead
94
109
  """
95
110
  self.logger = get_logger(__name__)
96
-
97
- # METRICS: Initialize deployment metrics tracking
98
- # This data structure would be used for collecting deployment telemetry
111
+
112
+ # Initialize template builder service
113
+ self.template_builder = AgentTemplateBuilder()
114
+
115
+ # Initialize version manager service
116
+ self.version_manager = AgentVersionManager()
117
+
118
+ # Initialize metrics collector service
119
+ self.metrics_collector = AgentMetricsCollector()
120
+
121
+ # Initialize environment manager service
122
+ self.environment_manager = AgentEnvironmentManager()
123
+
124
+ # Initialize validator service
125
+ self.validator = AgentValidator()
126
+
127
+ # Initialize filesystem manager service
128
+ self.filesystem_manager = AgentFileSystemManager()
129
+
130
+ # Initialize deployment metrics tracking
99
131
  self._deployment_metrics = {
100
- 'total_deployments': 0,
101
- 'successful_deployments': 0,
102
- 'failed_deployments': 0,
103
- 'migrations_performed': 0,
104
- 'average_deployment_time_ms': 0.0,
105
- 'deployment_times': [], # Keep last 100 for rolling average
106
- 'agent_type_counts': {}, # Track deployments by agent type
107
- 'version_migration_count': 0,
108
- 'template_validation_times': {}, # Track validation performance
109
- 'deployment_errors': {} # Track error types and frequencies
132
+ "total_deployments": 0,
133
+ "successful_deployments": 0,
134
+ "failed_deployments": 0,
135
+ "migrations_performed": 0,
136
+ "version_migration_count": 0,
137
+ "agent_type_counts": {},
138
+ "deployment_errors": {},
110
139
  }
111
-
140
+
112
141
  # Determine the actual working directory
113
142
  # For deployment, we need to track the working directory but NOT use it
114
143
  # for determining where system agents go (they always go to ~/.claude/agents/)
@@ -118,9 +147,9 @@ class AgentDeploymentService(AgentDeploymentInterface):
118
147
  else:
119
148
  # Use current directory but don't let CLAUDE_MPM_USER_PWD affect system agent deployment
120
149
  self.working_directory = Path.cwd()
121
-
150
+
122
151
  self.logger.info(f"Working directory for deployment: {self.working_directory}")
123
-
152
+
124
153
  # Find templates directory using centralized path management
125
154
  if templates_dir:
126
155
  self.templates_dir = Path(templates_dir)
@@ -129,32 +158,48 @@ class AgentDeploymentService(AgentDeploymentInterface):
129
158
  # For system agents, still use templates subdirectory
130
159
  # For project/user agents, this should be overridden with actual agents dir
131
160
  self.templates_dir = paths.agents_dir / "templates"
132
-
161
+
162
+ # Initialize discovery service (after templates_dir is set)
163
+ self.discovery_service = AgentDiscoveryService(self.templates_dir)
164
+
133
165
  # Find base agent file
134
166
  if base_agent_path:
135
167
  self.base_agent_path = Path(base_agent_path)
136
168
  else:
137
169
  # Use centralized paths for consistency
138
170
  self.base_agent_path = paths.agents_dir / "base_agent.json"
139
-
171
+
172
+ # Initialize configuration manager (after base_agent_path is set)
173
+ self.configuration_manager = AgentConfigurationManager(self.base_agent_path)
174
+
175
+ # Initialize format converter service
176
+ self.format_converter = AgentFormatConverter()
177
+
140
178
  self.logger.info(f"Templates directory: {self.templates_dir}")
141
179
  self.logger.info(f"Base agent path: {self.base_agent_path}")
142
-
143
- def deploy_agents(self, target_dir: Optional[Path] = None, force_rebuild: bool = False, deployment_mode: str = "update", config: Optional[Config] = None, use_async: bool = False) -> Dict[str, Any]:
180
+
181
+ def deploy_agents(
182
+ self,
183
+ target_dir: Optional[Path] = None,
184
+ force_rebuild: bool = False,
185
+ deployment_mode: str = "update",
186
+ config: Optional[Config] = None,
187
+ use_async: bool = False,
188
+ ) -> Dict[str, Any]:
144
189
  """
145
190
  Build and deploy agents by combining base_agent.md with templates.
146
191
  Also deploys system instructions for PM framework.
147
-
192
+
148
193
  DEPLOYMENT MODES:
149
194
  - "update": Normal update mode - skip agents with matching versions (default)
150
195
  - "project": Project deployment mode - always deploy all agents regardless of version
151
-
196
+
152
197
  CONFIGURATION:
153
198
  The config parameter or default configuration is used to determine:
154
199
  - Which agents to exclude from deployment
155
200
  - Case sensitivity for agent name matching
156
201
  - Whether to exclude agent dependencies
157
-
202
+
158
203
  METRICS COLLECTED:
159
204
  - Deployment start/end timestamps
160
205
  - Individual agent deployment durations
@@ -162,7 +207,7 @@ class AgentDeploymentService(AgentDeploymentInterface):
162
207
  - Version migration statistics
163
208
  - Template validation performance
164
209
  - Error type frequencies
165
-
210
+
166
211
  OPERATIONAL FLOW:
167
212
  0. Validates and repairs broken frontmatter in existing agents (Step 0)
168
213
  1. Validates target directory (creates if needed)
@@ -173,32 +218,32 @@ class AgentDeploymentService(AgentDeploymentInterface):
173
218
  - Builds YAML configuration
174
219
  - Writes to target directory
175
220
  - Tracks deployment status
176
-
221
+
177
222
  PERFORMANCE CONSIDERATIONS:
178
223
  - Skips unchanged agents (version-based caching)
179
224
  - Batch processes all agents in single pass
180
225
  - Minimal file I/O with in-memory building
181
226
  - Parallel-safe (no shared state mutations)
182
-
227
+
183
228
  ERROR HANDLING:
184
229
  - Continues deployment on individual agent failures
185
230
  - Collects all errors for reporting
186
231
  - Logs detailed error context
187
232
  - Returns comprehensive results dict
188
-
233
+
189
234
  MONITORING POINTS:
190
235
  - Track total deployment time
191
236
  - Monitor skipped vs updated vs new agents
192
237
  - Check error rates and patterns
193
238
  - Verify migration completion
194
-
239
+
195
240
  Args:
196
241
  target_dir: Target directory for agents (default: .claude/agents/)
197
242
  force_rebuild: Force rebuild even if agents exist (useful for troubleshooting)
198
243
  deployment_mode: "update" for version-aware updates, "project" for always deploy
199
244
  config: Optional configuration object (loads default if not provided)
200
245
  use_async: Use async operations for 50-70% faster deployment (default: True)
201
-
246
+
202
247
  Returns:
203
248
  Dictionary with deployment results:
204
249
  - target_dir: Deployment location
@@ -212,61 +257,63 @@ class AgentDeploymentService(AgentDeploymentInterface):
212
257
  """
213
258
  # METRICS: Record deployment start time for performance tracking
214
259
  deployment_start_time = time.time()
215
-
260
+
216
261
  # Try async deployment for better performance if requested
217
262
  if use_async:
218
263
  async_results = self._try_async_deployment(
219
264
  target_dir=target_dir,
220
265
  force_rebuild=force_rebuild,
221
266
  config=config,
222
- deployment_start_time=deployment_start_time
267
+ deployment_start_time=deployment_start_time,
223
268
  )
224
269
  if async_results is not None:
225
270
  return async_results
226
-
271
+
227
272
  # Continue with synchronous deployment
228
273
  self.logger.info("Using synchronous deployment")
229
-
274
+
230
275
  # Load and process configuration
231
276
  config, excluded_agents = self._load_deployment_config(config)
232
-
277
+
233
278
  # Determine target agents directory
234
279
  agents_dir = self._determine_agents_directory(target_dir)
235
-
280
+
236
281
  # Initialize results dictionary
237
282
  results = self._initialize_deployment_results(agents_dir, deployment_start_time)
238
-
283
+
239
284
  try:
240
285
  # Create agents directory if needed
241
286
  agents_dir.mkdir(parents=True, exist_ok=True)
242
-
287
+
243
288
  # STEP 0: Validate and repair broken frontmatter in existing agents
244
289
  self._repair_existing_agents(agents_dir, results)
245
-
290
+
246
291
  # Log deployment source tier
247
292
  source_tier = self._determine_source_tier()
248
- self.logger.info(f"Building and deploying {source_tier} agents to: {agents_dir}")
249
-
293
+ self.logger.info(
294
+ f"Building and deploying {source_tier} agents to: {agents_dir}"
295
+ )
296
+
250
297
  # Note: System instructions are now loaded directly by SimpleClaudeRunner
251
-
298
+
252
299
  # Check if templates directory exists
253
300
  if not self.templates_dir.exists():
254
301
  error_msg = f"Agents directory not found: {self.templates_dir}"
255
302
  self.logger.error(error_msg)
256
303
  results["errors"].append(error_msg)
257
304
  return results
258
-
305
+
259
306
  # Convert any existing YAML files to MD format
260
307
  conversion_results = self._convert_yaml_to_md(agents_dir)
261
308
  results["converted"] = conversion_results.get("converted", [])
262
-
309
+
263
310
  # Load base agent content
264
311
  base_agent_data, base_agent_version = self._load_base_agent()
265
-
312
+
266
313
  # Get and filter template files
267
314
  template_files = self._get_filtered_templates(excluded_agents, config)
268
315
  results["total"] = len(template_files)
269
-
316
+
270
317
  # Deploy each agent template
271
318
  for template_file in template_files:
272
319
  self._deploy_single_agent(
@@ -276,12 +323,12 @@ class AgentDeploymentService(AgentDeploymentInterface):
276
323
  base_agent_version=base_agent_version,
277
324
  force_rebuild=force_rebuild,
278
325
  deployment_mode=deployment_mode,
279
- results=results
326
+ results=results,
280
327
  )
281
-
328
+
282
329
  # Deploy system instructions and framework files
283
330
  self._deploy_system_instructions(agents_dir, force_rebuild, results)
284
-
331
+
285
332
  self.logger.info(
286
333
  f"Deployed {len(results['deployed'])} agents, "
287
334
  f"updated {len(results['updated'])}, "
@@ -291,7 +338,7 @@ class AgentDeploymentService(AgentDeploymentInterface):
291
338
  f"skipped {len(results['skipped'])}, "
292
339
  f"errors: {len(results['errors'])}"
293
340
  )
294
-
341
+
295
342
  except AgentDeploymentError as e:
296
343
  # Custom error with context already formatted
297
344
  self.logger.error(str(e))
@@ -301,679 +348,67 @@ class AgentDeploymentService(AgentDeploymentInterface):
301
348
  error_msg = f"Agent deployment failed: {e}"
302
349
  self.logger.error(error_msg)
303
350
  results["errors"].append(error_msg)
304
-
351
+
305
352
  # METRICS: Track deployment failure
306
- self._deployment_metrics['failed_deployments'] += 1
353
+ self._deployment_metrics["failed_deployments"] += 1
307
354
  error_type = type(e).__name__
308
- self._deployment_metrics['deployment_errors'][error_type] = \
309
- self._deployment_metrics['deployment_errors'].get(error_type, 0) + 1
310
-
355
+ self._deployment_metrics["deployment_errors"][error_type] = (
356
+ self._deployment_metrics["deployment_errors"].get(error_type, 0) + 1
357
+ )
358
+
311
359
  # METRICS: Calculate final deployment metrics
312
360
  deployment_end_time = time.time()
313
361
  deployment_duration = (deployment_end_time - deployment_start_time) * 1000 # ms
314
-
362
+
315
363
  results["metrics"]["end_time"] = deployment_end_time
316
364
  results["metrics"]["duration_ms"] = deployment_duration
317
-
365
+
318
366
  # METRICS: Update rolling averages and statistics
319
- self._update_deployment_metrics(deployment_duration, results)
320
-
367
+ self.metrics_collector.update_deployment_metrics(deployment_duration, results)
368
+
321
369
  return results
322
-
323
- def _update_deployment_metrics(self, duration_ms: float, results: Dict[str, Any]) -> None:
324
- """
325
- Update internal deployment metrics.
326
-
327
- METRICS TRACKING:
328
- - Rolling average of deployment times (last 100)
329
- - Success/failure rates
330
- - Agent type distribution
331
- - Version migration patterns
332
- - Error frequency analysis
333
-
334
- This method demonstrates ETL-like processing:
335
- 1. Extract: Gather raw metrics from deployment results
336
- 2. Transform: Calculate averages, rates, and distributions
337
- 3. Load: Store in internal metrics structure for reporting
338
- """
339
- # Update total deployment count
340
- self._deployment_metrics['total_deployments'] += 1
341
-
342
- # Track success/failure
343
- if not results.get('errors'):
344
- self._deployment_metrics['successful_deployments'] += 1
345
- else:
346
- self._deployment_metrics['failed_deployments'] += 1
347
-
348
- # Update rolling average deployment time
349
- self._deployment_metrics['deployment_times'].append(duration_ms)
350
- if len(self._deployment_metrics['deployment_times']) > 100:
351
- # Keep only last 100 for memory efficiency
352
- self._deployment_metrics['deployment_times'] = \
353
- self._deployment_metrics['deployment_times'][-100:]
354
-
355
- # Calculate new average
356
- if self._deployment_metrics['deployment_times']:
357
- self._deployment_metrics['average_deployment_time_ms'] = \
358
- sum(self._deployment_metrics['deployment_times']) / \
359
- len(self._deployment_metrics['deployment_times'])
360
-
370
+
361
371
  def get_deployment_metrics(self) -> Dict[str, Any]:
362
- """
363
- Get current deployment metrics.
364
-
365
- Returns:
366
- Dictionary containing:
367
- - Total deployments and success rates
368
- - Average deployment time
369
- - Agent type distribution
370
- - Migration statistics
371
- - Error analysis
372
-
373
- This demonstrates a metrics API endpoint that could be:
374
- - Exposed via REST API for monitoring tools
375
- - Pushed to time-series databases (Prometheus, InfluxDB)
376
- - Used for dashboards and alerting
377
- - Integrated with AI observability platforms
378
- """
379
- success_rate = 0.0
380
- if self._deployment_metrics['total_deployments'] > 0:
381
- success_rate = (self._deployment_metrics['successful_deployments'] /
382
- self._deployment_metrics['total_deployments']) * 100
383
-
384
- return {
385
- 'total_deployments': self._deployment_metrics['total_deployments'],
386
- 'successful_deployments': self._deployment_metrics['successful_deployments'],
387
- 'failed_deployments': self._deployment_metrics['failed_deployments'],
388
- 'success_rate_percent': success_rate,
389
- 'average_deployment_time_ms': self._deployment_metrics['average_deployment_time_ms'],
390
- 'migrations_performed': self._deployment_metrics['migrations_performed'],
391
- 'agent_type_distribution': self._deployment_metrics['agent_type_counts'].copy(),
392
- 'version_migrations': self._deployment_metrics['version_migration_count'],
393
- 'error_distribution': self._deployment_metrics['deployment_errors'].copy(),
394
- 'recent_deployment_times': self._deployment_metrics['deployment_times'][-10:] # Last 10
395
- }
396
-
372
+ """Get current deployment metrics."""
373
+ return self.metrics_collector.get_deployment_metrics()
374
+
397
375
  def reset_metrics(self) -> None:
398
- """
399
- Reset deployment metrics.
400
-
401
- Useful for:
402
- - Starting fresh metrics collection periods
403
- - Testing and development
404
- - Scheduled metric rotation (e.g., daily reset)
405
- """
406
- self._deployment_metrics = {
407
- 'total_deployments': 0,
408
- 'successful_deployments': 0,
409
- 'failed_deployments': 0,
410
- 'migrations_performed': 0,
411
- 'average_deployment_time_ms': 0.0,
412
- 'deployment_times': [],
413
- 'agent_type_counts': {},
414
- 'version_migration_count': 0,
415
- 'template_validation_times': {},
416
- 'deployment_errors': {}
417
- }
418
- self.logger.info("Deployment metrics reset")
419
-
420
- def _extract_version(self, content: str, version_marker: str) -> int:
421
- """
422
- Extract version number from content.
423
-
424
- Args:
425
- content: File content
426
- version_marker: Version marker to look for (e.g., "AGENT_VERSION:" or "BASE_AGENT_VERSION:")
427
-
428
- Returns:
429
- Version number or 0 if not found
430
- """
431
- import re
432
- pattern = rf"<!-- {version_marker} (\d+) -->"
433
- match = re.search(pattern, content)
434
- if match:
435
- return int(match.group(1))
436
- return 0
437
-
438
- def _build_agent_markdown(self, agent_name: str, template_path: Path, base_agent_data: dict) -> str:
439
- """
440
- Build a complete agent markdown file with YAML frontmatter.
441
-
442
- Args:
443
- agent_name: Name of the agent
444
- template_path: Path to the agent template JSON file
445
- base_agent_data: Base agent data from JSON
446
-
447
- Returns:
448
- Complete agent markdown content with YAML frontmatter
449
- """
450
- import json
451
- from datetime import datetime
452
-
453
- # Read template JSON
454
- template_data = json.loads(template_path.read_text())
455
-
456
- # Extract basic info
457
- # Handle both 'agent_version' (new format) and 'version' (old format)
458
- agent_version = self._parse_version(template_data.get('agent_version') or template_data.get('version', 0))
459
- base_version = self._parse_version(base_agent_data.get('base_version') or base_agent_data.get('version', 0))
460
-
461
- # Format version string as semantic version
462
- # Combine base and agent versions for a unified semantic version
463
- # Use agent version as primary, with base version in metadata
464
- version_string = self._format_version_display(agent_version)
465
-
466
- # Build YAML frontmatter
467
- # Check new format first (metadata.description), then old format
468
- description = (
469
- template_data.get('metadata', {}).get('description') or
470
- template_data.get('configuration_fields', {}).get('description') or
471
- template_data.get('description') or
472
- 'Agent for specialized tasks'
473
- )
474
-
475
- # Get tags from new format (metadata.tags) or old format
476
- tags = (
477
- template_data.get('metadata', {}).get('tags') or
478
- template_data.get('configuration_fields', {}).get('tags') or
479
- template_data.get('tags') or
480
- [agent_name, 'mpm-framework']
481
- )
482
-
483
- # Get tools from capabilities.tools in new format
484
- tools = (
485
- template_data.get('capabilities', {}).get('tools') or
486
- template_data.get('configuration_fields', {}).get('tools') or
487
- ["Read", "Write", "Edit", "Grep", "Glob", "LS"] # Default fallback
488
- )
489
-
490
- # Get model from capabilities.model in new format
491
- model = (
492
- template_data.get('capabilities', {}).get('model') or
493
- template_data.get('configuration_fields', {}).get('model') or
494
- "sonnet" # Default fallback
495
- )
496
-
497
- # Simplify model name for Claude Code
498
- model_map = {
499
- 'claude-4-sonnet-20250514': 'sonnet',
500
- 'claude-sonnet-4-20250514': 'sonnet',
501
- 'claude-opus-4-20250514': 'opus',
502
- 'claude-3-opus-20240229': 'opus',
503
- 'claude-3-haiku-20240307': 'haiku',
504
- 'claude-3.5-sonnet': 'sonnet',
505
- 'claude-3-sonnet': 'sonnet'
506
- }
507
- # Better fallback: extract the model type (opus/sonnet/haiku) from the string
508
- if model not in model_map:
509
- if 'opus' in model.lower():
510
- model = 'opus'
511
- elif 'sonnet' in model.lower():
512
- model = 'sonnet'
513
- elif 'haiku' in model.lower():
514
- model = 'haiku'
515
- else:
516
- # Last resort: try to extract from hyphenated format
517
- model = model_map.get(model, model.split('-')[-1] if '-' in model else model)
518
- else:
519
- model = model_map[model]
520
-
521
- # Get response format from template or use base agent default
522
- response_format = template_data.get('response', {}).get('format', 'structured')
523
-
524
- # Convert lists to space-separated strings for Claude Code compatibility
525
- tags_str = ' '.join(tags) if isinstance(tags, list) else tags
526
-
527
- # Convert tools list to comma-separated string for Claude Code compatibility
528
- # IMPORTANT: No spaces after commas - Claude Code requires exact format
529
- tools_str = ','.join(tools) if isinstance(tools, list) else tools
530
-
531
- # Validate tools format - CRITICAL: No spaces allowed!
532
- if ', ' in tools_str:
533
- self.logger.error(f"Tools contain spaces: '{tools_str}'")
534
- raise AgentDeploymentError(
535
- f"Tools must be comma-separated WITHOUT spaces",
536
- context={"agent_name": agent_name, "invalid_tools": tools_str}
537
- )
538
-
539
- # Extract proper agent_id and name from template
540
- agent_id = template_data.get('agent_id', agent_name)
541
- display_name = template_data.get('metadata', {}).get('name', agent_id)
542
-
543
- # Convert agent_id to Claude Code compatible name (replace underscores with hyphens)
544
- # Claude Code requires name to match pattern: ^[a-z0-9]+(-[a-z0-9]+)*$
545
- # CRITICAL: NO underscores allowed - they cause silent failures!
546
- claude_code_name = agent_id.replace('_', '-').lower()
547
-
548
- # Validate the name before proceeding
549
- import re
550
- if not re.match(r'^[a-z0-9]+(-[a-z0-9]+)*$', claude_code_name):
551
- self.logger.error(f"Invalid agent name '{claude_code_name}' - must match ^[a-z0-9]+(-[a-z0-9]+)*$")
552
- raise AgentDeploymentError(
553
- f"Agent name '{claude_code_name}' does not meet Claude Code requirements",
554
- context={"agent_name": agent_name, "invalid_name": claude_code_name}
555
- )
556
-
557
- # Build frontmatter with only the fields Claude Code uses
558
- frontmatter_lines = [
559
- "---",
560
- f"name: {claude_code_name}",
561
- f"description: {description}",
562
- f"version: {version_string}",
563
- f"base_version: {self._format_version_display(base_version)}",
564
- f"author: claude-mpm", # Identify as system agent for deployment
565
- f"tools: {tools_str}",
566
- f"model: {model}"
567
- ]
568
-
569
- # Add optional fields if present
570
- # Check for color in metadata section (new format) or root (old format)
571
- color = (
572
- template_data.get('metadata', {}).get('color') or
573
- template_data.get('color')
574
- )
575
- if color:
576
- frontmatter_lines.append(f"color: {color}")
577
-
578
- frontmatter_lines.append("---")
579
- frontmatter_lines.append("")
580
- frontmatter_lines.append("")
581
-
582
- frontmatter = '\n'.join(frontmatter_lines)
583
-
584
- # Get the main content (instructions)
585
- # Check multiple possible locations for instructions
586
- content = (
587
- template_data.get('instructions') or
588
- template_data.get('narrative_fields', {}).get('instructions') or
589
- template_data.get('content') or
590
- f"You are the {agent_name} agent. Perform tasks related to {template_data.get('description', 'your specialization')}."
591
- )
592
-
593
- return frontmatter + content
376
+ """Reset deployment metrics."""
377
+ return self.metrics_collector.reset_metrics()
594
378
 
595
- def _build_agent_yaml(self, agent_name: str, template_path: Path, base_agent_data: dict) -> str:
596
- """
597
- Build a complete agent YAML file by combining base agent and template.
598
- Only includes essential fields for Claude Code best practices.
599
-
600
- Args:
601
- agent_name: Name of the agent
602
- template_path: Path to the agent template JSON file
603
- base_agent_data: Base agent data from JSON
604
-
605
- Returns:
606
- Complete agent YAML content
607
- """
608
- import json
609
-
610
- # Read template JSON
611
- template_data = json.loads(template_path.read_text())
612
-
613
- # Extract capabilities
614
- capabilities = template_data.get('capabilities', {})
615
- metadata = template_data.get('metadata', {})
616
-
617
- # Extract version information
618
- agent_version = self._parse_version(template_data.get('agent_version') or template_data.get('version', 0))
619
- version_string = self._format_version_display(agent_version)
620
-
621
- # Get tools list
622
- tools = capabilities.get('tools', [])
623
- tools_str = ', '.join(tools) if tools else 'Read, Write, Edit, Grep, Glob, LS'
624
-
625
- # Get description
626
- description = (
627
- metadata.get('description') or
628
- template_data.get('description') or
629
- f'{agent_name.title()} agent for specialized tasks'
630
- )
631
-
632
- # Get priority based on agent type
633
- priority_map = {
634
- 'security': 'high',
635
- 'qa': 'high',
636
- 'engineer': 'high',
637
- 'documentation': 'medium',
638
- 'research': 'medium',
639
- 'ops': 'high',
640
- 'data_engineer': 'medium',
641
- 'version_control': 'high'
642
- }
643
- priority = priority_map.get(agent_name, 'medium')
644
-
645
- # Get model
646
- model = capabilities.get('model', 'claude-3-5-sonnet-20241022')
647
-
648
- # Get temperature
649
- temperature = capabilities.get('temperature', 0.3)
650
-
651
- # Build clean YAML frontmatter with only essential fields
652
- yaml_content = f"""---
653
- name: {agent_name}
654
- description: "{description}"
655
- version: "{version_string}"
656
- tools: {tools_str}
657
- priority: {priority}
658
- model: {model}
659
- temperature: {temperature}"""
660
-
661
- # Add allowed_tools if present
662
- if 'allowed_tools' in capabilities:
663
- yaml_content += f"\nallowed_tools: {json.dumps(capabilities['allowed_tools'])}"
664
-
665
- # Add disallowed_tools if present
666
- if 'disallowed_tools' in capabilities:
667
- yaml_content += f"\ndisallowed_tools: {json.dumps(capabilities['disallowed_tools'])}"
668
-
669
- yaml_content += "\n---\n"
670
-
671
- # Get instructions from template
672
- instructions = (
673
- template_data.get('instructions') or
674
- base_agent_data.get('narrative_fields', {}).get('instructions', '')
675
- )
676
-
677
- # Add base instructions if not already included
678
- base_instructions = base_agent_data.get('narrative_fields', {}).get('instructions', '')
679
- if base_instructions and base_instructions not in instructions:
680
- yaml_content += base_instructions + "\n\n---\n\n"
681
-
682
- yaml_content += instructions
683
-
684
- return yaml_content
685
-
686
- def _merge_narrative_fields(self, base_data: dict, template_data: dict) -> dict:
687
- """
688
- Merge narrative fields from base and template, combining arrays.
689
-
690
- Args:
691
- base_data: Base agent data
692
- template_data: Agent template data
693
-
694
- Returns:
695
- Merged narrative fields
696
- """
697
- base_narrative = base_data.get('narrative_fields', {})
698
- template_narrative = template_data.get('narrative_fields', {})
699
-
700
- merged = {}
701
-
702
- # For narrative fields, combine base + template
703
- for field in ['when_to_use', 'specialized_knowledge', 'unique_capabilities']:
704
- base_items = base_narrative.get(field, [])
705
- template_items = template_narrative.get(field, [])
706
- merged[field] = base_items + template_items
707
-
708
- # For instructions, combine with separator
709
- base_instructions = base_narrative.get('instructions', '')
710
- template_instructions = template_narrative.get('instructions', '')
711
-
712
- if base_instructions and template_instructions:
713
- merged['instructions'] = base_instructions + "\n\n---\n\n" + template_instructions
714
- elif template_instructions:
715
- merged['instructions'] = template_instructions
716
- elif base_instructions:
717
- merged['instructions'] = base_instructions
718
- else:
719
- merged['instructions'] = ''
720
-
721
- return merged
722
-
723
- def _merge_configuration_fields(self, base_data: dict, template_data: dict) -> dict:
724
- """
725
- Merge configuration fields, with template overriding base.
726
-
727
- Args:
728
- base_data: Base agent data
729
- template_data: Agent template data
730
-
731
- Returns:
732
- Merged configuration fields
733
- """
734
- base_config = base_data.get('configuration_fields', {})
735
- template_config = template_data.get('configuration_fields', {})
736
-
737
- # Start with base configuration
738
- merged = base_config.copy()
739
-
740
- # Override with template-specific configuration
741
- merged.update(template_config)
742
-
743
- # Also merge in capabilities from new format if not already in config
744
- capabilities = template_data.get('capabilities', {})
745
- if capabilities:
746
- # Map capabilities fields to configuration fields
747
- if 'tools' not in merged and 'tools' in capabilities:
748
- merged['tools'] = capabilities['tools']
749
- if 'max_tokens' not in merged and 'max_tokens' in capabilities:
750
- merged['max_tokens'] = capabilities['max_tokens']
751
- if 'temperature' not in merged and 'temperature' in capabilities:
752
- merged['temperature'] = capabilities['temperature']
753
- if 'timeout' not in merged and 'timeout' in capabilities:
754
- merged['timeout'] = capabilities['timeout']
755
- if 'memory_limit' not in merged and 'memory_limit' in capabilities:
756
- merged['memory_limit'] = capabilities['memory_limit']
757
- if 'cpu_limit' not in merged and 'cpu_limit' in capabilities:
758
- merged['cpu_limit'] = capabilities['cpu_limit']
759
- if 'network_access' not in merged and 'network_access' in capabilities:
760
- merged['network_access'] = capabilities['network_access']
761
- if 'model' not in merged and 'model' in capabilities:
762
- merged['model'] = capabilities['model']
763
-
764
- # Also check metadata for description and tags in new format
765
- metadata = template_data.get('metadata', {})
766
- if metadata:
767
- if 'description' not in merged and 'description' in metadata:
768
- merged['description'] = metadata['description']
769
- if 'tags' not in merged and 'tags' in metadata:
770
- merged['tags'] = metadata['tags']
771
-
772
- return merged
773
-
774
- def set_claude_environment(self, config_dir: Optional[Path] = None) -> Dict[str, str]:
775
- """
776
- Set Claude environment variables for agent discovery.
777
-
778
- OPERATIONAL PURPOSE:
779
- Claude Code discovers agents through environment variables that
780
- point to configuration directories. This method ensures proper
781
- environment setup for agent runtime discovery.
782
-
783
- ENVIRONMENT VARIABLES SET:
784
- 1. CLAUDE_CONFIG_DIR: Root configuration directory path
785
- 2. CLAUDE_MAX_PARALLEL_SUBAGENTS: Concurrency limit (default: 5)
786
- 3. CLAUDE_TIMEOUT: Agent execution timeout (default: 600s)
787
-
788
- DEPLOYMENT CONSIDERATIONS:
789
- - Call after agent deployment for immediate availability
790
- - Environment changes affect current process and children
791
- - Does not persist across system restarts
792
- - Add to shell profile for permanent configuration
793
-
794
- TROUBLESHOOTING:
795
- - Verify with: echo $CLAUDE_CONFIG_DIR
796
- - Check agent discovery: ls $CLAUDE_CONFIG_DIR/agents/
797
- - Monitor timeout issues in production
798
- - Adjust parallel limits based on system resources
799
-
800
- PERFORMANCE TUNING:
801
- - Increase parallel agents for CPU-bound tasks
802
- - Reduce for memory-constrained environments
803
- - Balance timeout with longest expected operations
804
- - Monitor resource usage during parallel execution
805
-
806
- Args:
807
- config_dir: Claude configuration directory (default: .claude/)
808
-
809
- Returns:
810
- Dictionary of environment variables set for verification
811
- """
379
+ def set_claude_environment(
380
+ self, config_dir: Optional[Path] = None
381
+ ) -> Dict[str, str]:
382
+ """Set Claude environment variables for agent discovery."""
812
383
  if not config_dir:
813
- # Use the working directory determined during initialization
814
384
  config_dir = self.working_directory / Paths.CLAUDE_CONFIG_DIR.value
815
-
816
- env_vars = {}
817
-
818
- # Set Claude configuration directory
819
- env_vars[EnvironmentVars.CLAUDE_CONFIG_DIR.value] = str(config_dir.absolute())
820
-
821
- # Set parallel agent limits
822
- env_vars[EnvironmentVars.CLAUDE_MAX_PARALLEL_SUBAGENTS.value] = EnvironmentVars.DEFAULT_MAX_AGENTS.value
823
-
824
- # Set timeout for agent execution
825
- env_vars[EnvironmentVars.CLAUDE_TIMEOUT.value] = EnvironmentVars.DEFAULT_TIMEOUT.value
826
-
827
- # Apply environment variables
828
- for key, value in env_vars.items():
829
- os.environ[key] = value
830
- self.logger.debug(f"Set environment: {key}={value}")
831
-
832
- return env_vars
833
-
385
+ return self.environment_manager.set_claude_environment(config_dir)
386
+
834
387
  def verify_deployment(self, config_dir: Optional[Path] = None) -> Dict[str, Any]:
835
- """
836
- Verify agent deployment and Claude configuration.
837
-
838
- OPERATIONAL PURPOSE:
839
- Post-deployment verification ensures agents are correctly deployed
840
- and discoverable by Claude Code. Critical for deployment validation
841
- and troubleshooting runtime issues.
842
-
843
- VERIFICATION CHECKS:
844
- 1. Configuration directory exists and is accessible
845
- 2. Agents directory contains expected YAML files
846
- 3. Agent files have valid YAML frontmatter
847
- 4. Version format is current (identifies migration needs)
848
- 5. Environment variables are properly set
849
-
850
- MONITORING INTEGRATION:
851
- - Call after deployment for health checks
852
- - Include in deployment pipelines
853
- - Log results for audit trails
854
- - Alert on missing agents or errors
855
-
856
- TROUBLESHOOTING GUIDE:
857
- - Missing config_dir: Check deployment target path
858
- - No agents found: Verify deployment completed
859
- - Migration needed: Run with force_rebuild
860
- - Environment warnings: Call set_claude_environment()
861
-
862
- RESULT INTERPRETATION:
863
- - agents_found: Successfully deployed agents
864
- - agents_needing_migration: Require version update
865
- - warnings: Non-critical issues to address
866
- - environment: Current runtime configuration
867
-
868
- Args:
869
- config_dir: Claude configuration directory (default: .claude/)
870
-
871
- Returns:
872
- Verification results dictionary:
873
- - config_dir: Checked directory path
874
- - agents_found: List of discovered agents with metadata
875
- - agents_needing_migration: Agents with old version format
876
- - environment: Current environment variables
877
- - warnings: List of potential issues
878
- """
388
+ """Verify agent deployment and Claude configuration."""
879
389
  if not config_dir:
880
- # Use the working directory determined during initialization
881
390
  config_dir = self.working_directory / ".claude"
882
-
883
- results = {
884
- "config_dir": str(config_dir),
885
- "agents_found": [],
886
- "agents_needing_migration": [],
887
- "environment": {},
888
- "warnings": []
889
- }
890
-
891
- # Check configuration directory
892
- if not config_dir.exists():
893
- results["warnings"].append(f"Configuration directory not found: {config_dir}")
894
- return results
895
-
896
- # Check agents directory
897
- agents_dir = config_dir / "agents"
898
- if not agents_dir.exists():
899
- results["warnings"].append(f"Agents directory not found: {agents_dir}")
900
- return results
901
-
902
- # List deployed agents
903
- agent_files = list(agents_dir.glob("*.md"))
904
-
905
- # Get exclusion configuration for logging purposes
906
- try:
907
- from claude_mpm.core.config import Config
908
- config = Config()
909
- excluded_agents = config.get('agent_deployment.excluded_agents', [])
910
- if excluded_agents:
911
- self.logger.debug(f"Note: The following agents are configured for exclusion: {excluded_agents}")
912
- except Exception:
913
- pass # Ignore config loading errors in verification
914
-
915
- for agent_file in agent_files:
916
- try:
917
- # Read first few lines to get agent name from YAML
918
- with open(agent_file, 'r') as f:
919
- lines = f.readlines()[:10]
920
-
921
- agent_info = {
922
- "file": agent_file.name,
923
- "path": str(agent_file)
924
- }
925
-
926
- # Extract name, version, and base_version from YAML frontmatter
927
- version_str = None
928
- base_version_str = None
929
- for line in lines:
930
- if line.startswith("name:"):
931
- agent_info["name"] = line.split(":", 1)[1].strip().strip('"\'')
932
- elif line.startswith("version:"):
933
- version_str = line.split(":", 1)[1].strip().strip('"\'')
934
- agent_info["version"] = version_str
935
- elif line.startswith("base_version:"):
936
- base_version_str = line.split(":", 1)[1].strip().strip('"\'')
937
- agent_info["base_version"] = base_version_str
938
-
939
- # Check if agent needs migration
940
- if version_str and self._is_old_version_format(version_str):
941
- agent_info["needs_migration"] = True
942
- results["agents_needing_migration"].append(agent_info["name"])
943
-
944
- results["agents_found"].append(agent_info)
945
-
946
- except Exception as e:
947
- results["warnings"].append(f"Failed to read {agent_file.name}: {e}")
948
-
949
- # Check environment variables
950
- env_vars = ["CLAUDE_CONFIG_DIR", "CLAUDE_MAX_PARALLEL_SUBAGENTS", "CLAUDE_TIMEOUT"]
951
- for var in env_vars:
952
- value = os.environ.get(var)
953
- if value:
954
- results["environment"][var] = value
955
- else:
956
- results["warnings"].append(f"Environment variable not set: {var}")
957
-
958
- return results
959
-
960
- def deploy_agent(self, agent_name: str, target_dir: Path, force_rebuild: bool = False) -> bool:
391
+ return self.validator.verify_deployment(config_dir)
392
+
393
+ def deploy_agent(
394
+ self, agent_name: str, target_dir: Path, force_rebuild: bool = False
395
+ ) -> bool:
961
396
  """
962
397
  Deploy a single agent to the specified directory.
963
-
398
+
964
399
  Args:
965
400
  agent_name: Name of the agent to deploy
966
401
  target_dir: Target directory for deployment (Path object)
967
402
  force_rebuild: Whether to force rebuild even if version is current
968
-
403
+
969
404
  Returns:
970
405
  True if deployment was successful, False otherwise
971
-
406
+
972
407
  WHY: Single agent deployment because:
973
408
  - Users may want to deploy specific agents only
974
409
  - Reduces deployment time for targeted updates
975
410
  - Enables selective agent management in projects
976
-
411
+
977
412
  FIXED: Method now correctly handles all internal calls to:
978
413
  - _check_agent_needs_update (with 3 arguments)
979
414
  - _build_agent_markdown (with 3 arguments including base_agent_data)
@@ -985,14 +420,14 @@ temperature: {temperature}"""
985
420
  if not template_file.exists():
986
421
  self.logger.error(f"Agent template not found: {agent_name}")
987
422
  return False
988
-
423
+
989
424
  # Ensure target directory exists
990
- agents_dir = target_dir / '.claude' / 'agents'
425
+ agents_dir = target_dir / ".claude" / "agents"
991
426
  agents_dir.mkdir(parents=True, exist_ok=True)
992
-
427
+
993
428
  # Build and deploy the agent
994
429
  target_file = agents_dir / f"{agent_name}.md"
995
-
430
+
996
431
  # Check if update is needed
997
432
  if not force_rebuild and target_file.exists():
998
433
  # Load base agent data for version checking
@@ -1001,39 +436,52 @@ temperature: {temperature}"""
1001
436
  if self.base_agent_path.exists():
1002
437
  try:
1003
438
  import json
439
+
1004
440
  base_agent_data = json.loads(self.base_agent_path.read_text())
1005
- base_agent_version = self._parse_version(base_agent_data.get('base_version') or base_agent_data.get('version', 0))
441
+ base_agent_version = self.version_manager.parse_version(
442
+ base_agent_data.get("base_version")
443
+ or base_agent_data.get("version", 0)
444
+ )
1006
445
  except Exception as e:
1007
- self.logger.warning(f"Could not load base agent for version check: {e}")
1008
-
1009
- needs_update, reason = self._check_agent_needs_update(target_file, template_file, base_agent_version)
446
+ self.logger.warning(
447
+ f"Could not load base agent for version check: {e}"
448
+ )
449
+
450
+ needs_update, reason = self.version_manager.check_agent_needs_update(
451
+ target_file, template_file, base_agent_version
452
+ )
1010
453
  if not needs_update:
1011
454
  self.logger.info(f"Agent {agent_name} is up to date")
1012
455
  return True
1013
456
  else:
1014
457
  self.logger.info(f"Updating agent {agent_name}: {reason}")
1015
-
458
+
1016
459
  # Load base agent data for building
1017
460
  base_agent_data = {}
1018
461
  if self.base_agent_path.exists():
1019
462
  try:
1020
463
  import json
464
+
1021
465
  base_agent_data = json.loads(self.base_agent_path.read_text())
1022
466
  except Exception as e:
1023
467
  self.logger.warning(f"Could not load base agent: {e}")
1024
-
468
+
1025
469
  # Build the agent markdown
1026
- agent_content = self._build_agent_markdown(agent_name, template_file, base_agent_data)
470
+ agent_content = self.template_builder.build_agent_markdown(
471
+ agent_name, template_file, base_agent_data
472
+ )
1027
473
  if not agent_content:
1028
474
  self.logger.error(f"Failed to build agent content for {agent_name}")
1029
475
  return False
1030
-
476
+
1031
477
  # Write to target file
1032
478
  target_file.write_text(agent_content)
1033
- self.logger.info(f"Successfully deployed agent: {agent_name} to {target_file}")
1034
-
479
+ self.logger.info(
480
+ f"Successfully deployed agent: {agent_name} to {target_file}"
481
+ )
482
+
1035
483
  return True
1036
-
484
+
1037
485
  except AgentDeploymentError:
1038
486
  # Re-raise our custom exceptions
1039
487
  raise
@@ -1041,943 +489,83 @@ temperature: {temperature}"""
1041
489
  # Wrap generic exceptions with context
1042
490
  raise AgentDeploymentError(
1043
491
  f"Failed to deploy agent {agent_name}",
1044
- context={"agent_name": agent_name, "error": str(e)}
492
+ context={"agent_name": agent_name, "error": str(e)},
1045
493
  ) from e
1046
-
494
+
1047
495
  def list_available_agents(self) -> List[Dict[str, Any]]:
1048
- """
1049
- List available agent templates.
1050
-
1051
- Returns:
1052
- List of agent information dictionaries
1053
- """
1054
- agents = []
1055
-
1056
- if not self.templates_dir.exists():
1057
- self.logger.warning(f"Agents directory not found: {self.templates_dir}")
1058
- return agents
1059
-
1060
- template_files = sorted(self.templates_dir.glob("*.json"))
1061
-
1062
- # Load configuration for exclusions
1063
- try:
1064
- from claude_mpm.core.config import Config
1065
- config = Config()
1066
- excluded_agents = config.get('agent_deployment.excluded_agents', [])
1067
- case_sensitive = config.get('agent_deployment.case_sensitive', False)
1068
-
1069
- # Normalize excluded agents for comparison
1070
- if not case_sensitive:
1071
- excluded_agents = [agent.lower() for agent in excluded_agents]
1072
- except Exception:
1073
- # If config loading fails, use empty exclusion list
1074
- excluded_agents = []
1075
- case_sensitive = False
1076
-
1077
- # Build combined exclusion set
1078
- hardcoded_exclusions = {"__init__", "MEMORIES", "TODOWRITE", "INSTRUCTIONS", "README", "pm", "PM", "project_manager"}
1079
-
1080
- # Filter out excluded agents
1081
- filtered_files = []
1082
- for f in template_files:
1083
- agent_name = f.stem
1084
-
1085
- # Check hardcoded exclusions
1086
- if agent_name in hardcoded_exclusions:
1087
- continue
1088
-
1089
- # Check file patterns
1090
- if agent_name.startswith(".") or agent_name.endswith(".backup"):
1091
- continue
1092
-
1093
- # Check user-configured exclusions
1094
- compare_name = agent_name.lower() if not case_sensitive else agent_name
1095
- if compare_name in excluded_agents:
1096
- continue
1097
-
1098
- filtered_files.append(f)
1099
-
1100
- template_files = filtered_files
1101
-
1102
- for template_file in template_files:
1103
- try:
1104
- agent_name = template_file.stem
1105
- agent_info = {
1106
- "name": agent_name,
1107
- "file": template_file.name,
1108
- "path": str(template_file),
1109
- "size": template_file.stat().st_size,
1110
- "description": f"{agent_name.title()} agent for specialized tasks"
1111
- }
1112
-
1113
- # Try to extract metadata from template JSON
1114
- try:
1115
- import json
1116
- template_data = json.loads(template_file.read_text())
1117
-
1118
- # Handle different schema formats
1119
- if 'metadata' in template_data:
1120
- # New schema format
1121
- metadata = template_data.get('metadata', {})
1122
- agent_info["description"] = metadata.get('description', agent_info["description"])
1123
- agent_info["role"] = metadata.get('specializations', [''])[0] if metadata.get('specializations') else ''
1124
- elif 'configuration_fields' in template_data:
1125
- # Old schema format
1126
- config_fields = template_data.get('configuration_fields', {})
1127
- agent_info["role"] = config_fields.get('primary_role', '')
1128
- agent_info["description"] = config_fields.get('description', agent_info["description"])
1129
-
1130
- # Handle both 'agent_version' (new format) and 'version' (old format)
1131
- version_tuple = self._parse_version(template_data.get('agent_version') or template_data.get('version', 0))
1132
- agent_info["version"] = self._format_version_display(version_tuple)
1133
-
1134
- except Exception:
1135
- pass # Use defaults if can't parse
1136
-
1137
- agents.append(agent_info)
1138
-
1139
- except Exception as e:
1140
- self.logger.error(f"Failed to read template {template_file.name}: {e}")
1141
-
1142
- return agents
1143
-
1144
- def _check_agent_needs_update(self, deployed_file: Path, template_file: Path, current_base_version: tuple) -> tuple:
1145
- """
1146
- Check if a deployed agent needs to be updated.
1147
-
1148
- OPERATIONAL LOGIC:
1149
- 1. Verifies agent is system-managed (claude-mpm authored)
1150
- 2. Extracts version from deployed YAML frontmatter
1151
- 3. Detects old version formats requiring migration
1152
- 4. Compares semantic versions for update decision
1153
- 5. Returns detailed reason for update/skip decision
1154
-
1155
- VERSION MIGRATION STRATEGY:
1156
- - Old serial format (0002-0005) -> Semantic (2.5.0)
1157
- - Missing versions -> Force update to latest
1158
- - Non-semantic formats -> Trigger migration
1159
- - Preserves user modifications (non-system agents)
1160
-
1161
- PERFORMANCE OPTIMIZATION:
1162
- - Early exit for non-system agents
1163
- - Regex compilation cached by Python
1164
- - Minimal file reads (frontmatter only)
1165
- - Version comparison without full parse
1166
-
1167
- ERROR RECOVERY:
1168
- - Assumes update needed on parse failures
1169
- - Logs warnings for investigation
1170
- - Never blocks deployment pipeline
1171
- - Safe fallback to force update
1172
-
1173
- Args:
1174
- deployed_file: Path to the deployed agent file
1175
- template_file: Path to the template file
1176
- current_base_version: Current base agent version (unused in new strategy)
1177
-
1178
- Returns:
1179
- Tuple of (needs_update: bool, reason: str)
1180
- - needs_update: True if agent should be redeployed
1181
- - reason: Human-readable explanation for decision
1182
- """
1183
- try:
1184
- # Read deployed agent content
1185
- deployed_content = deployed_file.read_text()
1186
-
1187
- # Check if it's a system agent (authored by claude-mpm)
1188
- if "claude-mpm" not in deployed_content:
1189
- return (False, "not a system agent")
1190
-
1191
- # Extract version info from YAML frontmatter
1192
- import re
1193
-
1194
- # Check if using old serial format first
1195
- is_old_format = False
1196
- old_version_str = None
1197
-
1198
- # Try legacy combined format (e.g., "0002-0005")
1199
- legacy_match = re.search(r'^version:\s*["\']?(\d+)-(\d+)["\']?', deployed_content, re.MULTILINE)
1200
- if legacy_match:
1201
- is_old_format = True
1202
- old_version_str = f"{legacy_match.group(1)}-{legacy_match.group(2)}"
1203
- # Convert legacy format to semantic version
1204
- # Treat the agent version (second number) as minor version
1205
- deployed_agent_version = (0, int(legacy_match.group(2)), 0)
1206
- self.logger.info(f"Detected old serial version format: {old_version_str}")
1207
- else:
1208
- # Try to extract semantic version format (e.g., "2.1.0")
1209
- version_match = re.search(r'^version:\s*["\']?v?(\d+)\.(\d+)\.(\d+)["\']?', deployed_content, re.MULTILINE)
1210
- if version_match:
1211
- deployed_agent_version = (int(version_match.group(1)), int(version_match.group(2)), int(version_match.group(3)))
1212
- else:
1213
- # Fallback: try separate fields (very old format)
1214
- agent_version_match = re.search(r"^agent_version:\s*(\d+)", deployed_content, re.MULTILINE)
1215
- if agent_version_match:
1216
- is_old_format = True
1217
- old_version_str = f"agent_version: {agent_version_match.group(1)}"
1218
- deployed_agent_version = (0, int(agent_version_match.group(1)), 0)
1219
- self.logger.info(f"Detected old separate version format: {old_version_str}")
1220
- else:
1221
- # Check for missing version field
1222
- if "version:" not in deployed_content:
1223
- is_old_format = True
1224
- old_version_str = "missing"
1225
- deployed_agent_version = (0, 0, 0)
1226
- self.logger.info("Detected missing version field")
1227
- else:
1228
- deployed_agent_version = (0, 0, 0)
1229
-
1230
- # For base version, we don't need to extract from deployed file anymore
1231
- # as it's tracked in metadata
1232
-
1233
- # Read template to get current agent version
1234
- import json
1235
- template_data = json.loads(template_file.read_text())
1236
-
1237
- # Extract agent version from template (handle both numeric and semantic versioning)
1238
- current_agent_version = self._parse_version(template_data.get('agent_version') or template_data.get('version', 0))
1239
-
1240
- # Compare semantic versions properly
1241
- # Semantic version comparison: compare major, then minor, then patch
1242
- def compare_versions(v1: tuple, v2: tuple) -> int:
1243
- """Compare two version tuples. Returns -1 if v1 < v2, 0 if equal, 1 if v1 > v2."""
1244
- for a, b in zip(v1, v2):
1245
- if a < b:
1246
- return -1
1247
- elif a > b:
1248
- return 1
1249
- return 0
1250
-
1251
- # If old format detected, always trigger update for migration
1252
- if is_old_format:
1253
- new_version_str = self._format_version_display(current_agent_version)
1254
- return (True, f"migration needed from old format ({old_version_str}) to semantic version ({new_version_str})")
1255
-
1256
- # Check if agent template version is newer
1257
- if compare_versions(current_agent_version, deployed_agent_version) > 0:
1258
- deployed_str = self._format_version_display(deployed_agent_version)
1259
- current_str = self._format_version_display(current_agent_version)
1260
- return (True, f"agent template updated ({deployed_str} -> {current_str})")
1261
-
1262
- # Note: We no longer check base agent version separately since we're using
1263
- # a unified semantic version for the agent
1264
-
1265
- return (False, "up to date")
1266
-
1267
- except Exception as e:
1268
- self.logger.warning(f"Error checking agent update status: {e}")
1269
- # On error, assume update is needed
1270
- return (True, "version check failed")
1271
-
496
+ """List available agent templates."""
497
+ return self.discovery_service.list_available_agents()
498
+
1272
499
  def clean_deployment(self, config_dir: Optional[Path] = None) -> Dict[str, Any]:
1273
- """
1274
- Clean up deployed agents.
1275
-
1276
- Args:
1277
- config_dir: Claude configuration directory (default: .claude/)
1278
-
1279
- Returns:
1280
- Cleanup results
1281
- """
500
+ """Clean up deployed agents."""
1282
501
  if not config_dir:
1283
- # Use the working directory determined during initialization
1284
502
  config_dir = self.working_directory / ".claude"
1285
-
1286
- results = {
1287
- "removed": [],
1288
- "errors": []
1289
- }
1290
-
1291
- agents_dir = config_dir / "agents"
1292
- if not agents_dir.exists():
1293
- results["errors"].append(f"Agents directory not found: {agents_dir}")
1294
- return results
1295
-
1296
- # Remove system agents only (identified by claude-mpm author)
1297
- agent_files = list(agents_dir.glob("*.md"))
1298
-
1299
- for agent_file in agent_files:
1300
- try:
1301
- # Check if it's a system agent
1302
- with open(agent_file, 'r') as f:
1303
- content = f.read()
1304
- if "author: claude-mpm" in content or "author: 'claude-mpm'" in content:
1305
- agent_file.unlink()
1306
- results["removed"].append(str(agent_file))
1307
- self.logger.debug(f"Removed agent: {agent_file.name}")
1308
-
1309
- except Exception as e:
1310
- error_msg = f"Failed to remove {agent_file.name}: {e}"
1311
- self.logger.error(error_msg)
1312
- results["errors"].append(error_msg)
1313
- # Not raising AgentDeploymentError here to continue cleanup
1314
-
1315
- return results
1316
-
1317
- def _extract_agent_metadata(self, template_content: str) -> Dict[str, Any]:
1318
- """
1319
- Extract metadata from simplified agent template content.
1320
-
1321
- Args:
1322
- template_content: Agent template markdown content
1323
-
1324
- Returns:
1325
- Dictionary of extracted metadata
1326
- """
1327
- metadata = {}
1328
- lines = template_content.split('\n')
1329
-
1330
- # Extract sections based on the new simplified format
1331
- current_section = None
1332
- section_content = []
1333
-
1334
- for line in lines:
1335
- line = line.strip()
1336
-
1337
- if line.startswith('## When to Use'):
1338
- # Save previous section before starting new one
1339
- if current_section and section_content:
1340
- metadata[current_section] = section_content.copy()
1341
- current_section = 'when_to_use'
1342
- section_content = []
1343
- elif line.startswith('## Specialized Knowledge'):
1344
- # Save previous section before starting new one
1345
- if current_section and section_content:
1346
- metadata[current_section] = section_content.copy()
1347
- current_section = 'specialized_knowledge'
1348
- section_content = []
1349
- elif line.startswith('## Unique Capabilities'):
1350
- # Save previous section before starting new one
1351
- if current_section and section_content:
1352
- metadata[current_section] = section_content.copy()
1353
- current_section = 'unique_capabilities'
1354
- section_content = []
1355
- elif line.startswith('## ') or line.startswith('# '):
1356
- # End of section - save current section
1357
- if current_section and section_content:
1358
- metadata[current_section] = section_content.copy()
1359
- current_section = None
1360
- section_content = []
1361
- elif current_section and line.startswith('- '):
1362
- # Extract list item, removing the "- " prefix
1363
- item = line[2:].strip()
1364
- if item:
1365
- section_content.append(item)
1366
-
1367
- # Handle last section if file ends without another header
1368
- if current_section and section_content:
1369
- metadata[current_section] = section_content.copy()
1370
-
1371
- # Ensure all required fields have defaults
1372
- metadata.setdefault('when_to_use', [])
1373
- metadata.setdefault('specialized_knowledge', [])
1374
- metadata.setdefault('unique_capabilities', [])
1375
-
1376
- return metadata
1377
-
503
+ return self.filesystem_manager.clean_deployment(config_dir)
504
+
1378
505
  def _get_agent_tools(self, agent_name: str, metadata: Dict[str, Any]) -> List[str]:
1379
- """
1380
- Get appropriate tools for an agent based on its type.
1381
-
1382
- Args:
1383
- agent_name: Name of the agent
1384
- metadata: Agent metadata
1385
-
1386
- Returns:
1387
- List of tool names
1388
- """
1389
- # Base tools all agents should have
1390
- base_tools = [
1391
- "Read",
1392
- "Write",
1393
- "Edit",
1394
- "MultiEdit",
1395
- "Grep",
1396
- "Glob",
1397
- "LS",
1398
- "TodoWrite"
1399
- ]
1400
-
1401
- # Agent-specific tools
1402
- agent_tools = {
1403
- 'engineer': base_tools + ["Bash", "WebSearch", "WebFetch"],
1404
- 'qa': base_tools + ["Bash", "WebSearch"],
1405
- 'documentation': base_tools + ["WebSearch", "WebFetch"],
1406
- 'research': base_tools + ["WebSearch", "WebFetch", "Bash"],
1407
- 'security': base_tools + ["Bash", "WebSearch", "Grep"],
1408
- 'ops': base_tools + ["Bash", "WebSearch"],
1409
- 'data_engineer': base_tools + ["Bash", "WebSearch"],
1410
- 'version_control': base_tools + ["Bash"]
1411
- }
1412
-
1413
- # Return specific tools or default set
1414
- return agent_tools.get(agent_name, base_tools + ["Bash", "WebSearch"])
1415
-
1416
- def _format_version_display(self, version_tuple: tuple) -> str:
1417
- """
1418
- Format version tuple for display.
1419
-
1420
- Args:
1421
- version_tuple: Tuple of (major, minor, patch)
1422
-
1423
- Returns:
1424
- Formatted version string
1425
- """
1426
- if isinstance(version_tuple, tuple) and len(version_tuple) == 3:
1427
- major, minor, patch = version_tuple
1428
- return f"{major}.{minor}.{patch}"
1429
- else:
1430
- # Fallback for legacy format
1431
- return str(version_tuple)
1432
-
1433
- def _is_old_version_format(self, version_str: str) -> bool:
1434
- """
1435
- Check if a version string is in the old serial format.
1436
-
1437
- Old formats include:
1438
- - Serial format: "0002-0005" (contains hyphen, all digits)
1439
- - Missing version field
1440
- - Non-semantic version formats
1441
-
1442
- Args:
1443
- version_str: Version string to check
1444
-
1445
- Returns:
1446
- True if old format, False if semantic version
1447
- """
1448
- if not version_str:
1449
- return True
1450
-
1451
- import re
1452
-
1453
- # Check for serial format (e.g., "0002-0005")
1454
- if re.match(r'^\d+-\d+$', version_str):
1455
- return True
1456
-
1457
- # Check for semantic version format (e.g., "2.1.0")
1458
- if re.match(r'^v?\d+\.\d+\.\d+$', version_str):
1459
- return False
1460
-
1461
- # Any other format is considered old
1462
- return True
1463
-
1464
- def _parse_version(self, version_value: Any) -> tuple:
1465
- """
1466
- Parse version from various formats to semantic version tuple.
1467
-
1468
- Handles:
1469
- - Integer values: 5 -> (0, 5, 0)
1470
- - String integers: "5" -> (0, 5, 0)
1471
- - Semantic versions: "2.1.0" -> (2, 1, 0)
1472
- - Invalid formats: returns (0, 0, 0)
1473
-
1474
- Args:
1475
- version_value: Version in various formats
1476
-
1477
- Returns:
1478
- Tuple of (major, minor, patch) for comparison
1479
- """
1480
- if isinstance(version_value, int):
1481
- # Legacy integer version - treat as minor version
1482
- return (0, version_value, 0)
1483
-
1484
- if isinstance(version_value, str):
1485
- # Try to parse as simple integer
1486
- if version_value.isdigit():
1487
- return (0, int(version_value), 0)
1488
-
1489
- # Try to parse semantic version (e.g., "2.1.0" or "v2.1.0")
1490
- import re
1491
- sem_ver_match = re.match(r'^v?(\d+)\.(\d+)\.(\d+)', version_value)
1492
- if sem_ver_match:
1493
- major = int(sem_ver_match.group(1))
1494
- minor = int(sem_ver_match.group(2))
1495
- patch = int(sem_ver_match.group(3))
1496
- return (major, minor, patch)
1497
-
1498
- # Try to extract first number from string as minor version
1499
- num_match = re.search(r'(\d+)', version_value)
1500
- if num_match:
1501
- return (0, int(num_match.group(1)), 0)
1502
-
1503
- # Default to 0.0.0 for invalid formats
1504
- return (0, 0, 0)
1505
-
1506
- def _format_yaml_list(self, items: List[str], indent: int) -> str:
1507
- """
1508
- Format a list for YAML with proper indentation.
1509
-
1510
- Args:
1511
- items: List of items
1512
- indent: Number of spaces to indent
1513
-
1514
- Returns:
1515
- Formatted YAML list string
1516
- """
1517
- if not items:
1518
- items = ["No items specified"]
1519
-
1520
- indent_str = " " * indent
1521
- formatted_items = []
1522
-
1523
- for item in items:
1524
- # Escape quotes in the item
1525
- item = item.replace('"', '\\"')
1526
- formatted_items.append(f'{indent_str}- "{item}"')
1527
-
1528
- return '\n'.join(formatted_items)
1529
-
506
+ """Get appropriate tools for an agent based on its type."""
507
+ from .agent_config_provider import AgentConfigProvider
508
+
509
+ return AgentConfigProvider.get_agent_tools(agent_name, metadata)
510
+
1530
511
  def _get_agent_specific_config(self, agent_name: str) -> Dict[str, Any]:
1531
- """
1532
- Get agent-specific configuration based on agent type.
1533
-
1534
- Args:
1535
- agent_name: Name of the agent
1536
-
1537
- Returns:
1538
- Dictionary of agent-specific configuration
1539
- """
1540
- # Base configuration all agents share
1541
- base_config = {
1542
- 'timeout': TimeoutConfig.DEFAULT_TIMEOUT,
1543
- 'max_tokens': SystemLimits.MAX_TOKEN_LIMIT,
1544
- 'memory_limit': ResourceLimits.STANDARD_MEMORY_RANGE[0], # Use lower bound of standard memory
1545
- 'cpu_limit': ResourceLimits.STANDARD_CPU_RANGE[1], # Use upper bound of standard CPU
1546
- 'network_access': True,
1547
- }
1548
-
1549
- # Agent-specific configurations
1550
- configs = {
1551
- 'engineer': {
1552
- **base_config,
1553
- 'description': 'Code implementation, development, and inline documentation',
1554
- 'tags': '["engineer", "development", "coding", "implementation"]',
1555
- 'tools': '["Read", "Write", "Edit", "MultiEdit", "Bash", "Grep", "Glob", "LS", "WebSearch", "TodoWrite"]',
1556
- 'temperature': 0.2,
1557
- 'when_to_use': ['Code implementation needed', 'Bug fixes required', 'Refactoring tasks'],
1558
- 'specialized_knowledge': ['Programming best practices', 'Design patterns', 'Code optimization'],
1559
- 'unique_capabilities': ['Write production code', 'Debug complex issues', 'Refactor codebases'],
1560
- 'primary_role': 'Code implementation and development',
1561
- 'specializations': '["coding", "debugging", "refactoring", "optimization"]',
1562
- 'authority': 'ALL code implementation decisions',
1563
- },
1564
- 'qa': {
1565
- **base_config,
1566
- 'description': 'Quality assurance, testing, and validation',
1567
- 'tags': '["qa", "testing", "quality", "validation"]',
1568
- 'tools': '["Read", "Write", "Edit", "Bash", "Grep", "Glob", "LS", "TodoWrite"]',
1569
- 'temperature': 0.1,
1570
- 'when_to_use': ['Testing needed', 'Quality validation', 'Test coverage analysis'],
1571
- 'specialized_knowledge': ['Testing methodologies', 'Quality metrics', 'Test automation'],
1572
- 'unique_capabilities': ['Execute test suites', 'Identify edge cases', 'Validate quality'],
1573
- 'primary_role': 'Testing and quality assurance',
1574
- 'specializations': '["testing", "validation", "quality-assurance", "coverage"]',
1575
- 'authority': 'ALL testing and quality decisions',
1576
- },
1577
- 'documentation': {
1578
- **base_config,
1579
- 'description': 'Documentation creation, maintenance, and changelog generation',
1580
- 'tags': '["documentation", "writing", "changelog", "docs"]',
1581
- 'tools': '["Read", "Write", "Edit", "MultiEdit", "Grep", "Glob", "LS", "WebSearch", "TodoWrite"]',
1582
- 'temperature': 0.3,
1583
- 'when_to_use': ['Documentation updates needed', 'Changelog generation', 'README updates'],
1584
- 'specialized_knowledge': ['Technical writing', 'Documentation standards', 'Semantic versioning'],
1585
- 'unique_capabilities': ['Create clear documentation', 'Generate changelogs', 'Maintain docs'],
1586
- 'primary_role': 'Documentation and technical writing',
1587
- 'specializations': '["technical-writing", "changelog", "api-docs", "guides"]',
1588
- 'authority': 'ALL documentation decisions',
1589
- },
1590
- 'research': {
1591
- **base_config,
1592
- 'description': 'Technical research, analysis, and investigation',
1593
- 'tags': '["research", "analysis", "investigation", "evaluation"]',
1594
- 'tools': '["Read", "Grep", "Glob", "LS", "WebSearch", "WebFetch", "TodoWrite"]',
1595
- 'temperature': 0.4,
1596
- 'when_to_use': ['Technical research needed', 'Solution evaluation', 'Best practices investigation'],
1597
- 'specialized_knowledge': ['Research methodologies', 'Technical analysis', 'Evaluation frameworks'],
1598
- 'unique_capabilities': ['Deep investigation', 'Comparative analysis', 'Evidence-based recommendations'],
1599
- 'primary_role': 'Research and technical analysis',
1600
- 'specializations': '["investigation", "analysis", "evaluation", "recommendations"]',
1601
- 'authority': 'ALL research decisions',
1602
- },
1603
- 'security': {
1604
- **base_config,
1605
- 'description': 'Security analysis, vulnerability assessment, and protection',
1606
- 'tags': '["security", "vulnerability", "protection", "audit"]',
1607
- 'tools': '["Read", "Grep", "Glob", "LS", "Bash", "WebSearch", "TodoWrite"]',
1608
- 'temperature': 0.1,
1609
- 'when_to_use': ['Security review needed', 'Vulnerability assessment', 'Security audit'],
1610
- 'specialized_knowledge': ['Security best practices', 'OWASP guidelines', 'Vulnerability patterns'],
1611
- 'unique_capabilities': ['Identify vulnerabilities', 'Security auditing', 'Threat modeling'],
1612
- 'primary_role': 'Security analysis and protection',
1613
- 'specializations': '["vulnerability-assessment", "security-audit", "threat-modeling", "protection"]',
1614
- 'authority': 'ALL security decisions',
1615
- },
1616
- 'ops': {
1617
- **base_config,
1618
- 'description': 'Deployment, operations, and infrastructure management',
1619
- 'tags': '["ops", "deployment", "infrastructure", "devops"]',
1620
- 'tools': '["Read", "Write", "Edit", "Bash", "Grep", "Glob", "LS", "TodoWrite"]',
1621
- 'temperature': 0.2,
1622
- 'when_to_use': ['Deployment configuration', 'Infrastructure setup', 'CI/CD pipeline work'],
1623
- 'specialized_knowledge': ['Deployment best practices', 'Infrastructure as code', 'CI/CD'],
1624
- 'unique_capabilities': ['Configure deployments', 'Manage infrastructure', 'Automate operations'],
1625
- 'primary_role': 'Operations and deployment management',
1626
- 'specializations': '["deployment", "infrastructure", "automation", "monitoring"]',
1627
- 'authority': 'ALL operations decisions',
1628
- },
1629
- 'data_engineer': {
1630
- **base_config,
1631
- 'description': 'Data pipeline management and AI API integrations',
1632
- 'tags': '["data", "pipeline", "etl", "ai-integration"]',
1633
- 'tools': '["Read", "Write", "Edit", "Bash", "Grep", "Glob", "LS", "WebSearch", "TodoWrite"]',
1634
- 'temperature': 0.2,
1635
- 'when_to_use': ['Data pipeline setup', 'Database design', 'AI API integration'],
1636
- 'specialized_knowledge': ['Data architectures', 'ETL processes', 'AI/ML APIs'],
1637
- 'unique_capabilities': ['Design data schemas', 'Build pipelines', 'Integrate AI services'],
1638
- 'primary_role': 'Data engineering and AI integration',
1639
- 'specializations': '["data-pipelines", "etl", "database", "ai-integration"]',
1640
- 'authority': 'ALL data engineering decisions',
1641
- },
1642
- 'version_control': {
1643
- **base_config,
1644
- 'description': 'Git operations, version management, and release coordination',
1645
- 'tags': '["git", "version-control", "release", "branching"]',
1646
- 'tools': '["Read", "Bash", "Grep", "Glob", "LS", "TodoWrite"]',
1647
- 'temperature': 0.1,
1648
- 'network_access': False, # Git operations are local
1649
- 'when_to_use': ['Git operations needed', 'Version bumping', 'Release management'],
1650
- 'specialized_knowledge': ['Git workflows', 'Semantic versioning', 'Release processes'],
1651
- 'unique_capabilities': ['Complex git operations', 'Version management', 'Release coordination'],
1652
- 'primary_role': 'Version control and release management',
1653
- 'specializations': '["git", "versioning", "branching", "releases"]',
1654
- 'authority': 'ALL version control decisions',
1655
- }
1656
- }
1657
-
1658
- # Return the specific config or a default
1659
- return configs.get(agent_name, {
1660
- **base_config,
1661
- 'description': f'{agent_name.title()} agent for specialized tasks',
1662
- 'tags': f'["{agent_name}", "specialized", "mpm"]',
1663
- 'tools': '["Read", "Write", "Edit", "Grep", "Glob", "LS", "TodoWrite"]',
1664
- 'temperature': 0.3,
1665
- 'when_to_use': [f'When {agent_name} expertise is needed'],
1666
- 'specialized_knowledge': [f'{agent_name.title()} domain knowledge'],
1667
- 'unique_capabilities': [f'{agent_name.title()} specialized operations'],
1668
- 'primary_role': f'{agent_name.title()} operations',
1669
- 'specializations': f'["{agent_name}"]',
1670
- 'authority': f'ALL {agent_name} decisions',
1671
- })
1672
-
1673
- def _deploy_system_instructions(self, target_dir: Path, force_rebuild: bool, results: Dict[str, Any]) -> None:
1674
- """
1675
- Deploy system instructions and framework files for PM framework.
1676
-
1677
- Deploys INSTRUCTIONS.md, WORKFLOW.md, and MEMORY.md files following hierarchy:
1678
- - System/User versions → Deploy to ~/.claude/
1679
- - Project-specific versions → Deploy to <project>/.claude/
1680
-
1681
- Args:
1682
- target_dir: Target directory for deployment
1683
- force_rebuild: Force rebuild even if exists
1684
- results: Results dictionary to update
1685
- """
1686
- try:
1687
- # Determine target location based on deployment type
1688
- if self._is_project_specific_deployment():
1689
- # Project-specific files go to project's .claude directory
1690
- claude_dir = self.working_directory / ".claude"
1691
- else:
1692
- # System and user files go to home ~/.claude directory
1693
- claude_dir = Path.home() / ".claude"
1694
-
1695
- # Ensure .claude directory exists
1696
- claude_dir.mkdir(parents=True, exist_ok=True)
1697
-
1698
- # Framework files to deploy
1699
- framework_files = [
1700
- ("INSTRUCTIONS.md", "CLAUDE.md"), # INSTRUCTIONS.md deploys as CLAUDE.md
1701
- ("WORKFLOW.md", "WORKFLOW.md"),
1702
- ("MEMORY.md", "MEMORY.md")
1703
- ]
1704
-
1705
- # Find the agents directory with framework files
1706
- # Use centralized paths for consistency
1707
- from claude_mpm.config.paths import paths
1708
- agents_path = paths.agents_dir
1709
-
1710
- for source_name, target_name in framework_files:
1711
- source_path = agents_path / source_name
1712
-
1713
- if not source_path.exists():
1714
- self.logger.warning(f"Framework file not found: {source_path}")
1715
- continue
1716
-
1717
- target_file = claude_dir / target_name
1718
-
1719
- # Check if update needed
1720
- if not force_rebuild and target_file.exists():
1721
- # Compare modification times
1722
- if target_file.stat().st_mtime >= source_path.stat().st_mtime:
1723
- results["skipped"].append(target_name)
1724
- self.logger.debug(f"Framework file {target_name} up to date")
1725
- continue
1726
-
1727
- # Read and deploy framework file
1728
- file_content = source_path.read_text()
1729
- target_file.write_text(file_content)
1730
-
1731
- # Track deployment
1732
- file_existed = target_file.exists()
1733
- deployment_info = {
1734
- "name": target_name,
1735
- "template": str(source_path),
1736
- "target": str(target_file)
1737
- }
1738
-
1739
- if file_existed:
1740
- results["updated"].append(deployment_info)
1741
- self.logger.info(f"Updated framework file: {target_name}")
1742
- else:
1743
- results["deployed"].append(deployment_info)
1744
- self.logger.info(f"Deployed framework file: {target_name}")
1745
-
1746
- except Exception as e:
1747
- error_msg = f"Failed to deploy system instructions: {e}"
1748
- self.logger.error(error_msg)
1749
- results["errors"].append(error_msg)
1750
- # Not raising AgentDeploymentError as this is non-critical
1751
-
512
+ """Get agent-specific configuration based on agent type."""
513
+ from .agent_config_provider import AgentConfigProvider
514
+
515
+ return AgentConfigProvider.get_agent_specific_config(agent_name)
516
+
517
+ def _deploy_system_instructions(
518
+ self, target_dir: Path, force_rebuild: bool, results: Dict[str, Any]
519
+ ) -> None:
520
+ """Deploy system instructions and framework files for PM framework."""
521
+ from .system_instructions_deployer import SystemInstructionsDeployer
522
+
523
+ deployer = SystemInstructionsDeployer(self.logger, self.working_directory)
524
+ deployer.deploy_system_instructions(
525
+ target_dir, force_rebuild, results, self._is_project_specific_deployment()
526
+ )
527
+
1752
528
  def _convert_yaml_to_md(self, target_dir: Path) -> Dict[str, Any]:
1753
- """
1754
- Convert existing YAML agent files to MD format with YAML frontmatter.
1755
-
1756
- This method handles backward compatibility by finding existing .yaml
1757
- agent files and converting them to .md format expected by Claude Code.
1758
-
1759
- Args:
1760
- target_dir: Directory containing agent files
1761
-
1762
- Returns:
1763
- Dictionary with conversion results:
1764
- - converted: List of converted files
1765
- - errors: List of conversion errors
1766
- - skipped: List of files that didn't need conversion
1767
- """
1768
- results = {
1769
- "converted": [],
1770
- "errors": [],
1771
- "skipped": []
1772
- }
1773
-
1774
- try:
1775
- # Find existing YAML agent files
1776
- yaml_files = list(target_dir.glob("*.yaml"))
1777
-
1778
- if not yaml_files:
1779
- self.logger.debug("No YAML files found to convert")
1780
- return results
1781
-
1782
- self.logger.info(f"Found {len(yaml_files)} YAML files to convert to MD format")
1783
-
1784
- for yaml_file in yaml_files:
1785
- try:
1786
- agent_name = yaml_file.stem
1787
- md_file = target_dir / f"{agent_name}.md"
1788
-
1789
- # Skip if MD file already exists (unless it's older than YAML)
1790
- if md_file.exists():
1791
- # Check modification times for safety
1792
- yaml_mtime = yaml_file.stat().st_mtime
1793
- md_mtime = md_file.stat().st_mtime
1794
-
1795
- if md_mtime >= yaml_mtime:
1796
- results["skipped"].append({
1797
- "yaml_file": str(yaml_file),
1798
- "md_file": str(md_file),
1799
- "reason": "MD file already exists and is newer"
1800
- })
1801
- continue
1802
- else:
1803
- # MD file is older, proceed with conversion
1804
- self.logger.info(f"MD file {md_file.name} is older than YAML, converting...")
1805
-
1806
- # Read YAML content
1807
- yaml_content = yaml_file.read_text()
1808
-
1809
- # Convert YAML to MD with YAML frontmatter
1810
- md_content = self._convert_yaml_content_to_md(yaml_content, agent_name)
1811
-
1812
- # Write MD file
1813
- md_file.write_text(md_content)
1814
-
1815
- # Create backup of YAML file before removing (for safety)
1816
- backup_file = target_dir / f"{agent_name}.yaml.backup"
1817
- try:
1818
- yaml_file.rename(backup_file)
1819
- self.logger.debug(f"Created backup: {backup_file.name}")
1820
- except Exception as backup_error:
1821
- self.logger.warning(f"Failed to create backup for {yaml_file.name}: {backup_error}")
1822
- # Still remove the original YAML file even if backup fails
1823
- yaml_file.unlink()
1824
-
1825
- results["converted"].append({
1826
- "from": str(yaml_file),
1827
- "to": str(md_file),
1828
- "agent": agent_name
1829
- })
1830
-
1831
- self.logger.info(f"Converted {yaml_file.name} to {md_file.name}")
1832
-
1833
- except Exception as e:
1834
- error_msg = f"Failed to convert {yaml_file.name}: {e}"
1835
- self.logger.error(error_msg)
1836
- results["errors"].append(error_msg)
1837
-
1838
- except Exception as e:
1839
- error_msg = f"YAML to MD conversion failed: {e}"
1840
- self.logger.error(error_msg)
1841
- results["errors"].append(error_msg)
1842
-
1843
- return results
1844
-
1845
- def _convert_yaml_content_to_md(self, yaml_content: str, agent_name: str) -> str:
1846
- """
1847
- Convert YAML agent content to MD format with YAML frontmatter.
1848
-
1849
- Args:
1850
- yaml_content: Original YAML content
1851
- agent_name: Name of the agent
1852
-
1853
- Returns:
1854
- Markdown content with YAML frontmatter
1855
- """
1856
- import re
1857
- from datetime import datetime
1858
-
1859
- # Extract YAML frontmatter and content
1860
- yaml_parts = yaml_content.split('---', 2)
1861
-
1862
- if len(yaml_parts) < 3:
1863
- # No proper YAML frontmatter, treat entire content as instructions
1864
- frontmatter = f"""---
1865
- name: {agent_name}
1866
- description: "Agent for specialized tasks"
1867
- version: "1.0.0"
1868
- author: "claude-mpm@anthropic.com"
1869
- created: "{datetime.now().isoformat()}Z"
1870
- updated: "{datetime.now().isoformat()}Z"
1871
- tags: ["{agent_name}", "mpm-framework"]
1872
- tools: ["Read", "Write", "Edit", "Grep", "Glob", "LS"]
1873
- metadata:
1874
- deployment_type: "system"
1875
- converted_from: "yaml"
1876
- ---
529
+ """Convert existing YAML agent files to MD format with YAML frontmatter."""
530
+ return self.format_converter.convert_yaml_to_md(target_dir)
1877
531
 
1878
- """
1879
- return frontmatter + yaml_content.strip()
1880
-
1881
- # Parse existing frontmatter
1882
- yaml_frontmatter = yaml_parts[1].strip()
1883
- instructions = yaml_parts[2].strip()
1884
-
1885
- # Extract key fields from YAML frontmatter
1886
- name = agent_name
1887
- description = self._extract_yaml_field(yaml_frontmatter, 'description') or f"{agent_name.title()} agent for specialized tasks"
1888
- version = self._extract_yaml_field(yaml_frontmatter, 'version') or "1.0.0"
1889
- tools_line = self._extract_yaml_field(yaml_frontmatter, 'tools') or "Read, Write, Edit, Grep, Glob, LS"
1890
-
1891
- # Convert tools string to list format
1892
- if isinstance(tools_line, str):
1893
- if tools_line.startswith('[') and tools_line.endswith(']'):
1894
- # Already in list format
1895
- tools_list = tools_line
1896
- else:
1897
- # Convert comma-separated to list
1898
- tools = [tool.strip() for tool in tools_line.split(',')]
1899
- tools_list = str(tools)
1900
- else:
1901
- tools_list = str(tools_line) if tools_line else '["Read", "Write", "Edit", "Grep", "Glob", "LS"]'
1902
-
1903
- # Build new YAML frontmatter
1904
- new_frontmatter = f"""---
1905
- name: {name}
1906
- description: "{description}"
1907
- version: "{version}"
1908
- author: "claude-mpm@anthropic.com"
1909
- created: "{datetime.now().isoformat()}Z"
1910
- updated: "{datetime.now().isoformat()}Z"
1911
- tags: ["{agent_name}", "mpm-framework"]
1912
- tools: {tools_list}
1913
- metadata:
1914
- deployment_type: "system"
1915
- converted_from: "yaml"
1916
- ---
532
+ def _convert_yaml_content_to_md(self, yaml_content: str, agent_name: str) -> str:
533
+ """Convert YAML agent content to MD format with YAML frontmatter."""
534
+ return self.format_converter.convert_yaml_content_to_md(
535
+ yaml_content, agent_name
536
+ )
1917
537
 
1918
- """
1919
-
1920
- return new_frontmatter + instructions
1921
-
1922
538
  def _extract_yaml_field(self, yaml_content: str, field_name: str) -> str:
1923
- """
1924
- Extract a field value from YAML content.
1925
-
1926
- Args:
1927
- yaml_content: YAML content string
1928
- field_name: Field name to extract
1929
-
1930
- Returns:
1931
- Field value or None if not found
1932
- """
1933
- import re
1934
-
1935
- try:
1936
- # Match field with quoted or unquoted values
1937
- pattern = rf'^{field_name}:\s*["\']?(.*?)["\']?\s*$'
1938
- match = re.search(pattern, yaml_content, re.MULTILINE)
1939
-
1940
- if match:
1941
- return match.group(1).strip()
1942
-
1943
- # Try with alternative spacing patterns
1944
- pattern = rf'^{field_name}\s*:\s*(.+)$'
1945
- match = re.search(pattern, yaml_content, re.MULTILINE)
1946
-
1947
- if match:
1948
- value = match.group(1).strip()
1949
- # Remove quotes if present
1950
- if (value.startswith('"') and value.endswith('"')) or \
1951
- (value.startswith("'") and value.endswith("'")):
1952
- value = value[1:-1]
1953
- return value
1954
-
1955
- except Exception as e:
1956
- self.logger.warning(f"Error extracting YAML field '{field_name}': {e}")
1957
-
1958
- return None
1959
-
1960
- def _try_async_deployment(self, target_dir: Optional[Path], force_rebuild: bool,
1961
- config: Optional[Config], deployment_start_time: float) -> Optional[Dict[str, Any]]:
539
+ """Extract a field value from YAML content."""
540
+ return self.format_converter.extract_yaml_field(yaml_content, field_name)
541
+
542
+ def _try_async_deployment(
543
+ self,
544
+ target_dir: Optional[Path],
545
+ force_rebuild: bool,
546
+ config: Optional[Config],
547
+ deployment_start_time: float,
548
+ ) -> Optional[Dict[str, Any]]:
1962
549
  """
1963
550
  Try to use async deployment for better performance.
1964
-
551
+
1965
552
  WHY: Async deployment is 50-70% faster than synchronous deployment
1966
553
  by using concurrent operations for file I/O and processing.
1967
-
554
+
1968
555
  Args:
1969
556
  target_dir: Target directory for deployment
1970
557
  force_rebuild: Whether to force rebuild
1971
558
  config: Configuration object
1972
559
  deployment_start_time: Start time for metrics
1973
-
560
+
1974
561
  Returns:
1975
562
  Deployment results if successful, None if async not available
1976
563
  """
1977
564
  try:
1978
565
  from .async_agent_deployment import deploy_agents_async_wrapper
566
+
1979
567
  self.logger.info("Using async deployment for improved performance")
1980
-
568
+
1981
569
  # Run async deployment
1982
570
  results = deploy_agents_async_wrapper(
1983
571
  templates_dir=self.templates_dir,
@@ -1985,187 +573,82 @@ metadata:
1985
573
  working_directory=self.working_directory,
1986
574
  target_dir=target_dir,
1987
575
  force_rebuild=force_rebuild,
1988
- config=config
576
+ config=config,
1989
577
  )
1990
-
578
+
1991
579
  # Add metrics about async vs sync
1992
- if 'metrics' in results:
1993
- results['metrics']['deployment_method'] = 'async'
1994
- duration_ms = results['metrics'].get('duration_ms', 0)
580
+ if "metrics" in results:
581
+ results["metrics"]["deployment_method"] = "async"
582
+ duration_ms = results["metrics"].get("duration_ms", 0)
1995
583
  self.logger.info(f"Async deployment completed in {duration_ms:.1f}ms")
1996
-
584
+
1997
585
  # Update internal metrics
1998
- self._deployment_metrics['total_deployments'] += 1
1999
- if not results.get('errors'):
2000
- self._deployment_metrics['successful_deployments'] += 1
586
+ self._deployment_metrics["total_deployments"] += 1
587
+ if not results.get("errors"):
588
+ self._deployment_metrics["successful_deployments"] += 1
2001
589
  else:
2002
- self._deployment_metrics['failed_deployments'] += 1
2003
-
590
+ self._deployment_metrics["failed_deployments"] += 1
591
+
2004
592
  return results
2005
-
593
+
2006
594
  except ImportError:
2007
595
  self.logger.warning("Async deployment not available, falling back to sync")
2008
596
  return None
2009
597
  except Exception as e:
2010
598
  self.logger.warning(f"Async deployment failed, falling back to sync: {e}")
2011
599
  return None
2012
-
600
+
2013
601
  def _load_deployment_config(self, config: Optional[Config]) -> tuple:
2014
- """
2015
- Load and process deployment configuration.
2016
-
2017
- WHY: Centralized configuration loading reduces duplication
2018
- and ensures consistent handling of exclusion settings.
2019
-
2020
- Args:
2021
- config: Optional configuration object
2022
-
2023
- Returns:
2024
- Tuple of (config, excluded_agents)
2025
- """
2026
- # Load configuration if not provided
2027
- if config is None:
2028
- config = Config()
2029
-
2030
- # Get agent exclusion configuration
2031
- excluded_agents = config.get('agent_deployment.excluded_agents', [])
2032
- case_sensitive = config.get('agent_deployment.case_sensitive', False)
2033
- exclude_dependencies = config.get('agent_deployment.exclude_dependencies', False)
2034
-
2035
- # Normalize excluded agents list for comparison
2036
- if not case_sensitive:
2037
- excluded_agents = [agent.lower() for agent in excluded_agents]
2038
-
2039
- # Log exclusion configuration if agents are being excluded
2040
- if excluded_agents:
2041
- self.logger.info(f"Excluding agents from deployment: {excluded_agents}")
2042
- self.logger.debug(f"Case sensitive matching: {case_sensitive}")
2043
- self.logger.debug(f"Exclude dependencies: {exclude_dependencies}")
2044
-
2045
- return config, excluded_agents
2046
-
602
+ """Load and process deployment configuration."""
603
+ from .deployment_config_loader import DeploymentConfigLoader
604
+
605
+ loader = DeploymentConfigLoader(self.logger)
606
+ return loader.load_deployment_config(config)
607
+
2047
608
  def _determine_agents_directory(self, target_dir: Optional[Path]) -> Path:
2048
- """
2049
- Determine the correct agents directory based on input.
2050
-
2051
- WHY: Different deployment scenarios require different directory
2052
- structures. This method centralizes the logic for consistency.
2053
-
2054
- HIERARCHY:
2055
- - System agents → Deploy to ~/.claude/agents/ (user's home directory)
2056
- - User custom agents from ~/.claude-mpm/agents/ → Deploy to ~/.claude/agents/
2057
- - Project-specific agents from <project>/.claude-mpm/agents/ → Deploy to <project>/.claude/agents/
2058
-
2059
- Args:
2060
- target_dir: Optional target directory
2061
-
2062
- Returns:
2063
- Path to agents directory
2064
- """
2065
- if not target_dir:
2066
- # Default deployment location depends on agent source
2067
- # Check if we're deploying system agents or user/project agents
2068
- if self._is_system_agent_deployment():
2069
- # System agents go to user's home ~/.claude/agents/
2070
- return Path.home() / ".claude" / "agents"
2071
- elif self._is_project_specific_deployment():
2072
- # Project agents stay in project directory
2073
- return self.working_directory / ".claude" / "agents"
2074
- else:
2075
- # Default: User custom agents go to home ~/.claude/agents/
2076
- return Path.home() / ".claude" / "agents"
2077
-
2078
- # If target_dir provided, use it directly (caller decides structure)
2079
- target_dir = Path(target_dir)
2080
-
2081
- # Check if this is already an agents directory
2082
- if target_dir.name == "agents":
2083
- # Already an agents directory, use as-is
2084
- return target_dir
2085
- elif target_dir.name == ".claude-mpm":
2086
- # .claude-mpm directory, add agents subdirectory
2087
- return target_dir / "agents"
2088
- elif target_dir.name == ".claude":
2089
- # .claude directory, add agents subdirectory
2090
- return target_dir / "agents"
2091
- else:
2092
- # Assume it's a project directory, add .claude/agents
2093
- return target_dir / ".claude" / "agents"
2094
-
609
+ """Determine the correct agents directory based on input."""
610
+ from .agents_directory_resolver import AgentsDirectoryResolver
611
+
612
+ resolver = AgentsDirectoryResolver(
613
+ self.working_directory,
614
+ self._is_system_agent_deployment(),
615
+ self._is_project_specific_deployment(),
616
+ )
617
+ return resolver.determine_agents_directory(target_dir)
618
+
2095
619
  def _is_system_agent_deployment(self) -> bool:
2096
- """
2097
- Check if this is a deployment of system agents.
2098
-
2099
- System agents are those provided by the claude-mpm package itself,
2100
- located in the package's agents/templates directory.
2101
-
2102
- Returns:
2103
- True if deploying system agents, False otherwise
2104
- """
2105
- # Check if templates_dir points to the system templates
2106
- if self.templates_dir and self.templates_dir.exists():
2107
- # System agents are in the package's agents/templates directory
2108
- try:
2109
- # Check if templates_dir is within the claude_mpm package structure
2110
- templates_str = str(self.templates_dir.resolve())
2111
- return ("site-packages/claude_mpm" in templates_str or
2112
- "src/claude_mpm/agents/templates" in templates_str or
2113
- (paths.agents_dir / "templates").resolve() == self.templates_dir.resolve())
2114
- except Exception:
2115
- pass
2116
- return False
2117
-
620
+ """Check if this is a deployment of system agents."""
621
+ from .deployment_type_detector import DeploymentTypeDetector
622
+
623
+ return DeploymentTypeDetector.is_system_agent_deployment(self.templates_dir)
624
+
2118
625
  def _is_project_specific_deployment(self) -> bool:
2119
- """
2120
- Check if deploying project-specific agents.
2121
-
2122
- Project-specific agents are those found in the project's
2123
- .claude-mpm/agents/ directory.
2124
-
2125
- Returns:
2126
- True if deploying project-specific agents, False otherwise
2127
- """
2128
- # Check if we're in a project directory with .claude-mpm/agents
2129
- project_agents_dir = self.working_directory / ".claude-mpm" / "agents"
2130
- if project_agents_dir.exists():
2131
- # Check if templates_dir points to project agents
2132
- if self.templates_dir and self.templates_dir.exists():
2133
- try:
2134
- return project_agents_dir.resolve() == self.templates_dir.resolve()
2135
- except Exception:
2136
- pass
2137
- return False
2138
-
626
+ """Check if deploying project-specific agents."""
627
+ from .deployment_type_detector import DeploymentTypeDetector
628
+
629
+ return DeploymentTypeDetector.is_project_specific_deployment(
630
+ self.templates_dir, self.working_directory
631
+ )
632
+
2139
633
  def _is_user_custom_deployment(self) -> bool:
2140
- """
2141
- Check if deploying user custom agents.
2142
-
2143
- User custom agents are those in ~/.claude-mpm/agents/
2144
-
2145
- Returns:
2146
- True if deploying user custom agents, False otherwise
2147
- """
2148
- user_agents_dir = Path.home() / ".claude-mpm" / "agents"
2149
- if user_agents_dir.exists():
2150
- # Check if templates_dir points to user agents
2151
- if self.templates_dir and self.templates_dir.exists():
2152
- try:
2153
- return user_agents_dir.resolve() == self.templates_dir.resolve()
2154
- except Exception:
2155
- pass
2156
- return False
634
+ """Check if deploying user custom agents."""
635
+ from .deployment_type_detector import DeploymentTypeDetector
636
+
637
+ return DeploymentTypeDetector.is_user_custom_deployment(self.templates_dir)
2157
638
 
2158
- def _initialize_deployment_results(self, agents_dir: Path, deployment_start_time: float) -> Dict[str, Any]:
639
+ def _initialize_deployment_results(
640
+ self, agents_dir: Path, deployment_start_time: float
641
+ ) -> Dict[str, Any]:
2159
642
  """
2160
643
  Initialize the deployment results dictionary.
2161
-
644
+
2162
645
  WHY: Consistent result structure ensures all deployment
2163
646
  operations return the same format for easier processing.
2164
-
647
+
2165
648
  Args:
2166
649
  agents_dir: Target agents directory
2167
650
  deployment_start_time: Start time for metrics
2168
-
651
+
2169
652
  Returns:
2170
653
  Initialized results dictionary
2171
654
  """
@@ -2186,17 +669,19 @@ metadata:
2186
669
  "duration_ms": None,
2187
670
  "agent_timings": {}, # Track individual agent deployment times
2188
671
  "validation_times": {}, # Track template validation times
2189
- "resource_usage": {} # Could track memory/CPU if needed
2190
- }
672
+ "resource_usage": {}, # Could track memory/CPU if needed
673
+ },
2191
674
  }
2192
-
2193
- def _repair_existing_agents(self, agents_dir: Path, results: Dict[str, Any]) -> None:
675
+
676
+ def _repair_existing_agents(
677
+ self, agents_dir: Path, results: Dict[str, Any]
678
+ ) -> None:
2194
679
  """
2195
680
  Validate and repair broken frontmatter in existing agents.
2196
-
681
+
2197
682
  WHY: Ensures all existing agents have valid YAML frontmatter
2198
683
  before deployment, preventing runtime errors in Claude Code.
2199
-
684
+
2200
685
  Args:
2201
686
  agents_dir: Directory containing agent files
2202
687
  results: Results dictionary to update
@@ -2204,124 +689,42 @@ metadata:
2204
689
  repair_results = self._validate_and_repair_existing_agents(agents_dir)
2205
690
  if repair_results["repaired"]:
2206
691
  results["repaired"] = repair_results["repaired"]
2207
- self.logger.info(f"Repaired frontmatter in {len(repair_results['repaired'])} existing agents")
692
+ self.logger.info(
693
+ f"Repaired frontmatter in {len(repair_results['repaired'])} existing agents"
694
+ )
2208
695
  for agent_name in repair_results["repaired"]:
2209
696
  self.logger.debug(f" - Repaired: {agent_name}")
2210
-
697
+
2211
698
  def _determine_source_tier(self) -> str:
2212
- """
2213
- Determine the source tier for logging.
2214
-
2215
- WHY: Understanding which tier (SYSTEM/USER/PROJECT) agents
2216
- are being deployed from helps with debugging and auditing.
2217
-
2218
- Returns:
2219
- Source tier string
2220
- """
2221
- if ".claude-mpm/agents" in str(self.templates_dir) and "/templates" not in str(self.templates_dir):
2222
- return "PROJECT"
2223
- elif "/.claude-mpm/agents" in str(self.templates_dir) and "/templates" not in str(self.templates_dir):
2224
- return "USER"
2225
- return "SYSTEM"
2226
-
699
+ """Determine the source tier for logging."""
700
+ from .deployment_type_detector import DeploymentTypeDetector
701
+
702
+ return DeploymentTypeDetector.determine_source_tier(self.templates_dir)
703
+
2227
704
  def _load_base_agent(self) -> tuple:
2228
- """
2229
- Load base agent content and version.
2230
-
2231
- WHY: Base agent contains shared configuration that all agents
2232
- inherit, reducing duplication and ensuring consistency.
2233
-
2234
- Returns:
2235
- Tuple of (base_agent_data, base_agent_version)
2236
- """
2237
- base_agent_data = {}
2238
- base_agent_version = (0, 0, 0)
2239
-
2240
- if self.base_agent_path.exists():
2241
- try:
2242
- import json
2243
- base_agent_data = json.loads(self.base_agent_path.read_text())
2244
- # Handle both 'base_version' (new format) and 'version' (old format)
2245
- # MIGRATION PATH: Supporting both formats during transition period
2246
- base_agent_version = self._parse_version(
2247
- base_agent_data.get('base_version') or base_agent_data.get('version', 0)
2248
- )
2249
- self.logger.info(f"Loaded base agent template (version {self._format_version_display(base_agent_version)})")
2250
- except Exception as e:
2251
- # NON-FATAL: Base agent is optional enhancement, not required
2252
- self.logger.warning(f"Could not load base agent: {e}")
2253
-
2254
- return base_agent_data, base_agent_version
2255
-
705
+ """Load base agent content and version."""
706
+ return self.configuration_manager.load_base_agent()
707
+
2256
708
  def _get_filtered_templates(self, excluded_agents: list, config: Config) -> list:
2257
- """
2258
- Get and filter template files based on exclusion rules.
2259
-
2260
- WHY: Centralized filtering logic ensures consistent exclusion
2261
- handling across different deployment scenarios.
2262
-
2263
- Args:
2264
- excluded_agents: List of agents to exclude
2265
- config: Configuration object
2266
-
2267
- Returns:
2268
- List of filtered template files
2269
- """
2270
- # Get all template files
2271
- template_files = list(self.templates_dir.glob("*.json"))
2272
-
2273
- # Build the combined exclusion set
2274
- # Start with hardcoded exclusions (these are ALWAYS excluded)
2275
- hardcoded_exclusions = {"__init__", "MEMORIES", "TODOWRITE", "INSTRUCTIONS",
2276
- "README", "pm", "PM", "project_manager"}
2277
-
2278
- # Get case sensitivity setting
2279
- case_sensitive = config.get('agent_deployment.case_sensitive', False)
2280
-
2281
- # Filter out excluded agents
2282
- filtered_files = []
2283
- excluded_count = 0
2284
-
2285
- for f in template_files:
2286
- agent_name = f.stem
2287
-
2288
- # Check hardcoded exclusions (always case-sensitive)
2289
- if agent_name in hardcoded_exclusions:
2290
- self.logger.debug(f"Excluding {agent_name}: hardcoded system exclusion")
2291
- excluded_count += 1
2292
- continue
2293
-
2294
- # Check file patterns
2295
- if agent_name.startswith(".") or agent_name.endswith(".backup"):
2296
- self.logger.debug(f"Excluding {agent_name}: file pattern exclusion")
2297
- excluded_count += 1
2298
- continue
2299
-
2300
- # Check user-configured exclusions
2301
- compare_name = agent_name.lower() if not case_sensitive else agent_name
2302
- if compare_name in excluded_agents:
2303
- self.logger.info(f"Excluding {agent_name}: user-configured exclusion")
2304
- excluded_count += 1
2305
- continue
2306
-
2307
- # Agent is not excluded, add to filtered list
2308
- filtered_files.append(f)
2309
-
2310
- if excluded_count > 0:
2311
- self.logger.info(f"Excluded {excluded_count} agents from deployment")
2312
-
2313
- return filtered_files
2314
-
2315
- def _deploy_single_agent(self, template_file: Path, agents_dir: Path,
2316
- base_agent_data: dict, base_agent_version: tuple,
2317
- force_rebuild: bool, deployment_mode: str,
2318
- results: Dict[str, Any]) -> None:
709
+ """Get and filter template files based on exclusion rules."""
710
+ return self.discovery_service.get_filtered_templates(excluded_agents, config)
711
+
712
+ def _deploy_single_agent(
713
+ self,
714
+ template_file: Path,
715
+ agents_dir: Path,
716
+ base_agent_data: dict,
717
+ base_agent_version: tuple,
718
+ force_rebuild: bool,
719
+ deployment_mode: str,
720
+ results: Dict[str, Any],
721
+ ) -> None:
2319
722
  """
2320
723
  Deploy a single agent template.
2321
-
724
+
2322
725
  WHY: Extracting single agent deployment logic reduces complexity
2323
726
  and makes the main deployment loop more readable.
2324
-
727
+
2325
728
  Args:
2326
729
  template_file: Agent template file
2327
730
  agents_dir: Target agents directory
@@ -2334,36 +737,50 @@ metadata:
2334
737
  try:
2335
738
  # METRICS: Track individual agent deployment time
2336
739
  agent_start_time = time.time()
2337
-
740
+
2338
741
  agent_name = template_file.stem
2339
742
  target_file = agents_dir / f"{agent_name}.md"
2340
-
743
+
2341
744
  # Check if agent needs update
2342
745
  needs_update, is_migration, reason = self._check_update_status(
2343
- target_file, template_file, base_agent_version,
2344
- force_rebuild, deployment_mode
746
+ target_file,
747
+ template_file,
748
+ base_agent_version,
749
+ force_rebuild,
750
+ deployment_mode,
2345
751
  )
2346
-
752
+
2347
753
  # Skip if exists and doesn't need update (only in update mode)
2348
- if target_file.exists() and not needs_update and deployment_mode != "project":
754
+ if (
755
+ target_file.exists()
756
+ and not needs_update
757
+ and deployment_mode != "project"
758
+ ):
2349
759
  results["skipped"].append(agent_name)
2350
760
  self.logger.debug(f"Skipped up-to-date agent: {agent_name}")
2351
761
  return
2352
-
762
+
2353
763
  # Build the agent file as markdown with YAML frontmatter
2354
- agent_content = self._build_agent_markdown(agent_name, template_file, base_agent_data)
2355
-
764
+ agent_content = self.template_builder.build_agent_markdown(
765
+ agent_name, template_file, base_agent_data
766
+ )
767
+
2356
768
  # Write the agent file
2357
769
  is_update = target_file.exists()
2358
770
  target_file.write_text(agent_content)
2359
-
771
+
2360
772
  # Record metrics and update results
2361
773
  self._record_agent_deployment(
2362
- agent_name, template_file, target_file,
2363
- is_update, is_migration, reason,
2364
- agent_start_time, results
774
+ agent_name,
775
+ template_file,
776
+ target_file,
777
+ is_update,
778
+ is_migration,
779
+ reason,
780
+ agent_start_time,
781
+ results,
2365
782
  )
2366
-
783
+
2367
784
  except AgentDeploymentError as e:
2368
785
  # Re-raise our custom exceptions
2369
786
  self.logger.error(str(e))
@@ -2373,40 +790,47 @@ metadata:
2373
790
  error_msg = f"Failed to build {template_file.name}: {e}"
2374
791
  self.logger.error(error_msg)
2375
792
  results["errors"].append(error_msg)
2376
-
2377
- def _check_update_status(self, target_file: Path, template_file: Path,
2378
- base_agent_version: tuple, force_rebuild: bool,
2379
- deployment_mode: str) -> tuple:
793
+
794
+ def _check_update_status(
795
+ self,
796
+ target_file: Path,
797
+ template_file: Path,
798
+ base_agent_version: tuple,
799
+ force_rebuild: bool,
800
+ deployment_mode: str,
801
+ ) -> tuple:
2380
802
  """
2381
803
  Check if agent needs update and determine status.
2382
-
804
+
2383
805
  WHY: Centralized update checking logic ensures consistent
2384
806
  version comparison and migration detection.
2385
-
807
+
2386
808
  Args:
2387
809
  target_file: Target agent file
2388
810
  template_file: Template file
2389
811
  base_agent_version: Base agent version
2390
812
  force_rebuild: Whether to force rebuild
2391
813
  deployment_mode: Deployment mode
2392
-
814
+
2393
815
  Returns:
2394
816
  Tuple of (needs_update, is_migration, reason)
2395
817
  """
2396
818
  needs_update = force_rebuild
2397
819
  is_migration = False
2398
820
  reason = ""
2399
-
821
+
2400
822
  # In project deployment mode, always deploy regardless of version
2401
823
  if deployment_mode == "project":
2402
824
  if target_file.exists():
2403
825
  needs_update = True
2404
- self.logger.debug(f"Project deployment mode: will deploy {template_file.stem}")
826
+ self.logger.debug(
827
+ f"Project deployment mode: will deploy {template_file.stem}"
828
+ )
2405
829
  else:
2406
830
  needs_update = True
2407
831
  elif not needs_update and target_file.exists():
2408
832
  # In update mode, check version compatibility
2409
- needs_update, reason = self._check_agent_needs_update(
833
+ needs_update, reason = self.version_manager.check_agent_needs_update(
2410
834
  target_file, template_file, base_agent_version
2411
835
  )
2412
836
  if needs_update:
@@ -2415,20 +839,29 @@ metadata:
2415
839
  is_migration = True
2416
840
  self.logger.info(f"Migrating agent {template_file.stem}: {reason}")
2417
841
  else:
2418
- self.logger.info(f"Agent {template_file.stem} needs update: {reason}")
2419
-
842
+ self.logger.info(
843
+ f"Agent {template_file.stem} needs update: {reason}"
844
+ )
845
+
2420
846
  return needs_update, is_migration, reason
2421
-
2422
- def _record_agent_deployment(self, agent_name: str, template_file: Path,
2423
- target_file: Path, is_update: bool,
2424
- is_migration: bool, reason: str,
2425
- agent_start_time: float, results: Dict[str, Any]) -> None:
847
+
848
+ def _record_agent_deployment(
849
+ self,
850
+ agent_name: str,
851
+ template_file: Path,
852
+ target_file: Path,
853
+ is_update: bool,
854
+ is_migration: bool,
855
+ reason: str,
856
+ agent_start_time: float,
857
+ results: Dict[str, Any],
858
+ ) -> None:
2426
859
  """
2427
860
  Record deployment metrics and update results.
2428
-
861
+
2429
862
  WHY: Centralized metrics recording ensures consistent tracking
2430
863
  of deployment performance and statistics.
2431
-
864
+
2432
865
  Args:
2433
866
  agent_name: Name of the agent
2434
867
  template_file: Template file
@@ -2442,226 +875,95 @@ metadata:
2442
875
  # METRICS: Record deployment time for this agent
2443
876
  agent_deployment_time = (time.time() - agent_start_time) * 1000 # Convert to ms
2444
877
  results["metrics"]["agent_timings"][agent_name] = agent_deployment_time
2445
-
878
+
2446
879
  # METRICS: Update agent type deployment counts
2447
- self._deployment_metrics['agent_type_counts'][agent_name] = \
2448
- self._deployment_metrics['agent_type_counts'].get(agent_name, 0) + 1
2449
-
880
+ self._deployment_metrics["agent_type_counts"][agent_name] = (
881
+ self._deployment_metrics["agent_type_counts"].get(agent_name, 0) + 1
882
+ )
883
+
2450
884
  deployment_info = {
2451
885
  "name": agent_name,
2452
886
  "template": str(template_file),
2453
887
  "target": str(target_file),
2454
- "deployment_time_ms": agent_deployment_time
888
+ "deployment_time_ms": agent_deployment_time,
2455
889
  }
2456
-
890
+
2457
891
  if is_migration:
2458
892
  deployment_info["reason"] = reason
2459
893
  results["migrated"].append(deployment_info)
2460
- self.logger.info(f"Successfully migrated agent: {agent_name} to semantic versioning")
2461
-
894
+ self.logger.info(
895
+ f"Successfully migrated agent: {agent_name} to semantic versioning"
896
+ )
897
+
2462
898
  # METRICS: Track migration statistics
2463
- self._deployment_metrics['migrations_performed'] += 1
2464
- self._deployment_metrics['version_migration_count'] += 1
2465
-
899
+ self._deployment_metrics["migrations_performed"] += 1
900
+ self._deployment_metrics["version_migration_count"] += 1
901
+
2466
902
  elif is_update:
2467
903
  results["updated"].append(deployment_info)
2468
904
  self.logger.debug(f"Updated agent: {agent_name}")
2469
905
  else:
2470
906
  results["deployed"].append(deployment_info)
2471
907
  self.logger.debug(f"Built and deployed agent: {agent_name}")
2472
-
908
+
2473
909
  def _validate_and_repair_existing_agents(self, agents_dir: Path) -> Dict[str, Any]:
2474
- """
2475
- Validate and repair broken frontmatter in existing agent files.
2476
-
2477
- This method scans existing .claude/agents/*.md files and validates their
2478
- frontmatter. If the frontmatter is broken or missing, it attempts to repair
2479
- it or marks the agent for replacement during deployment.
2480
-
2481
- WHY: Ensures all existing agents have valid YAML frontmatter before deployment,
2482
- preventing runtime errors in Claude Code when loading agents.
2483
-
2484
- Args:
2485
- agents_dir: Directory containing agent .md files
2486
-
2487
- Returns:
2488
- Dictionary with validation results:
2489
- - repaired: List of agent names that were repaired
2490
- - replaced: List of agent names marked for replacement
2491
- - errors: List of validation errors
2492
- """
2493
- results = {
2494
- "repaired": [],
2495
- "replaced": [],
2496
- "errors": []
2497
- }
2498
-
2499
- try:
2500
- # Import frontmatter validator
2501
- from claude_mpm.agents.frontmatter_validator import FrontmatterValidator
2502
- validator = FrontmatterValidator()
2503
-
2504
- # Find existing agent files
2505
- agent_files = list(agents_dir.glob("*.md"))
2506
-
2507
- if not agent_files:
2508
- self.logger.debug("No existing agent files to validate")
2509
- return results
2510
-
2511
- self.logger.debug(f"Validating frontmatter in {len(agent_files)} existing agents")
2512
-
2513
- for agent_file in agent_files:
2514
- try:
2515
- agent_name = agent_file.stem
2516
-
2517
- # Read agent file content
2518
- content = agent_file.read_text()
2519
-
2520
- # Check if this is a system agent (authored by claude-mpm)
2521
- # Only repair system agents, leave user agents alone
2522
- if "author: claude-mpm" not in content and "author: 'claude-mpm'" not in content:
2523
- self.logger.debug(f"Skipping validation for user agent: {agent_name}")
2524
- continue
2525
-
2526
- # Extract and validate frontmatter
2527
- if not content.startswith("---"):
2528
- # No frontmatter at all - mark for replacement
2529
- self.logger.warning(f"Agent {agent_name} has no frontmatter, marking for replacement")
2530
- results["replaced"].append(agent_name)
2531
- # Delete the file so it will be recreated
2532
- agent_file.unlink()
2533
- continue
2534
-
2535
- # Try to extract frontmatter
2536
- try:
2537
- end_marker = content.find("\n---\n", 4)
2538
- if end_marker == -1:
2539
- end_marker = content.find("\n---\r\n", 4)
2540
-
2541
- if end_marker == -1:
2542
- # Broken frontmatter - mark for replacement
2543
- self.logger.warning(f"Agent {agent_name} has broken frontmatter, marking for replacement")
2544
- results["replaced"].append(agent_name)
2545
- # Delete the file so it will be recreated
2546
- agent_file.unlink()
2547
- continue
2548
-
2549
- # Validate frontmatter with the validator
2550
- validation_result = validator.validate_file(agent_file)
2551
-
2552
- if not validation_result.is_valid:
2553
- # Check if it can be corrected
2554
- if validation_result.corrected_frontmatter:
2555
- # Apply corrections
2556
- correction_result = validator.correct_file(agent_file, dry_run=False)
2557
- if correction_result.corrections:
2558
- results["repaired"].append(agent_name)
2559
- self.logger.info(f"Repaired frontmatter for agent {agent_name}")
2560
- for correction in correction_result.corrections:
2561
- self.logger.debug(f" - {correction}")
2562
- else:
2563
- # Cannot be corrected - mark for replacement
2564
- self.logger.warning(f"Agent {agent_name} has invalid frontmatter that cannot be repaired, marking for replacement")
2565
- results["replaced"].append(agent_name)
2566
- # Delete the file so it will be recreated
2567
- agent_file.unlink()
2568
- elif validation_result.warnings:
2569
- # Has warnings but is valid
2570
- for warning in validation_result.warnings:
2571
- self.logger.debug(f"Agent {agent_name} warning: {warning}")
2572
-
2573
- except Exception as e:
2574
- # Any error in parsing - mark for replacement
2575
- self.logger.warning(f"Failed to parse frontmatter for {agent_name}: {e}, marking for replacement")
2576
- results["replaced"].append(agent_name)
2577
- # Delete the file so it will be recreated
2578
- try:
2579
- agent_file.unlink()
2580
- except Exception:
2581
- pass
2582
-
2583
- except Exception as e:
2584
- error_msg = f"Failed to validate agent {agent_file.name}: {e}"
2585
- self.logger.error(error_msg)
2586
- results["errors"].append(error_msg)
2587
-
2588
- except ImportError:
2589
- self.logger.warning("FrontmatterValidator not available, skipping validation")
2590
- except Exception as e:
2591
- error_msg = f"Agent validation failed: {e}"
2592
- self.logger.error(error_msg)
2593
- results["errors"].append(error_msg)
2594
-
2595
- return results
2596
-
910
+ """Validate and repair broken frontmatter in existing agent files."""
911
+ from .agent_frontmatter_validator import AgentFrontmatterValidator
912
+
913
+ validator = AgentFrontmatterValidator(self.logger)
914
+ return validator.validate_and_repair_existing_agents(agents_dir)
915
+
2597
916
  # ================================================================================
2598
917
  # Interface Adapter Methods
2599
918
  # ================================================================================
2600
919
  # These methods adapt the existing implementation to comply with AgentDeploymentInterface
2601
-
920
+
2602
921
  def validate_agent(self, agent_path: Path) -> tuple[bool, List[str]]:
2603
922
  """Validate agent configuration and structure.
2604
-
923
+
2605
924
  WHY: This adapter method provides interface compliance while leveraging
2606
925
  the existing validation logic in _check_agent_needs_update and other methods.
2607
-
926
+
2608
927
  Args:
2609
928
  agent_path: Path to agent configuration file
2610
-
929
+
2611
930
  Returns:
2612
931
  Tuple of (is_valid, list_of_errors)
2613
932
  """
2614
933
  errors = []
2615
-
934
+
2616
935
  try:
2617
936
  if not agent_path.exists():
2618
937
  return False, [f"Agent file not found: {agent_path}"]
2619
-
938
+
2620
939
  content = agent_path.read_text()
2621
-
940
+
2622
941
  # Check YAML frontmatter format
2623
942
  if not content.startswith("---"):
2624
943
  errors.append("Missing YAML frontmatter")
2625
-
944
+
2626
945
  # Extract and validate version
2627
946
  import re
2628
- version_match = re.search(r'^version:\s*["\']?(.+?)["\']?$', content, re.MULTILINE)
947
+
948
+ version_match = re.search(
949
+ r'^version:\s*["\']?(.+?)["\']?$', content, re.MULTILINE
950
+ )
2629
951
  if not version_match:
2630
952
  errors.append("Missing version field in frontmatter")
2631
-
953
+
2632
954
  # Check for required fields
2633
- required_fields = ['name', 'description', 'tools']
955
+ required_fields = ["name", "description", "tools"]
2634
956
  for field in required_fields:
2635
- field_match = re.search(rf'^{field}:\s*.+$', content, re.MULTILINE)
957
+ field_match = re.search(rf"^{field}:\s*.+$", content, re.MULTILINE)
2636
958
  if not field_match:
2637
959
  errors.append(f"Missing required field: {field}")
2638
-
960
+
2639
961
  # If no errors, validation passed
2640
962
  return len(errors) == 0, errors
2641
-
963
+
2642
964
  except Exception as e:
2643
965
  return False, [f"Validation error: {str(e)}"]
2644
-
966
+
2645
967
  def get_deployment_status(self) -> Dict[str, Any]:
2646
- """Get current deployment status and metrics.
2647
-
2648
- WHY: This adapter method provides interface compliance by wrapping
2649
- verify_deployment and adding deployment metrics.
2650
-
2651
- Returns:
2652
- Dictionary with deployment status information
2653
- """
2654
- # Get verification results
2655
- verification = self.verify_deployment()
2656
-
2657
- # Add deployment metrics
2658
- status = {
2659
- "deployment_metrics": self._deployment_metrics.copy(),
2660
- "verification": verification,
2661
- "agents_deployed": len(verification.get("agents_found", [])),
2662
- "agents_needing_migration": len(verification.get("agents_needing_migration", [])),
2663
- "has_warnings": len(verification.get("warnings", [])) > 0,
2664
- "environment_configured": bool(verification.get("environment", {}))
2665
- }
2666
-
2667
- return status
968
+ """Get current deployment status and metrics."""
969
+ return self.metrics_collector.get_deployment_status()