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
@@ -5,45 +5,60 @@ import os
5
5
  import subprocess
6
6
  import sys
7
7
  import time
8
+ import uuid
8
9
  from datetime import datetime
9
10
  from pathlib import Path
10
- from typing import Optional, TYPE_CHECKING
11
- import uuid
11
+ from typing import TYPE_CHECKING, Optional
12
+
12
13
  from claude_mpm.config.paths import paths
13
14
 
14
15
  # Core imports that don't cause circular dependencies
15
16
  from claude_mpm.core.config import Config
16
- from claude_mpm.core.logging_config import get_logger, log_operation, log_performance_context
17
- from claude_mpm.core.logger import get_project_logger, ProjectLogger
18
- from claude_mpm.core.container import get_container, ServiceLifetime
17
+ from claude_mpm.core.container import ServiceLifetime, get_container
19
18
  from claude_mpm.core.interfaces import (
20
19
  AgentDeploymentInterface,
21
- TicketManagerInterface,
22
- HookServiceInterface
20
+ HookServiceInterface,
21
+ TicketManagerInterface,
22
+ )
23
+ from claude_mpm.core.logger import ProjectLogger, get_project_logger
24
+ from claude_mpm.core.logging_config import (
25
+ get_logger,
26
+ log_operation,
27
+ log_performance_context,
28
+ )
29
+ from claude_mpm.services.core.interfaces import (
30
+ AgentCapabilitiesInterface,
31
+ CommandHandlerInterface,
32
+ MemoryHookInterface,
33
+ RunnerConfigurationInterface,
34
+ SessionManagementInterface,
35
+ SubprocessLauncherInterface,
36
+ SystemInstructionsInterface,
37
+ UtilityServiceInterface,
38
+ VersionServiceInterface,
23
39
  )
24
40
 
25
41
  # Type checking imports to avoid circular dependencies
26
42
  if TYPE_CHECKING:
27
43
  from claude_mpm.services.agents.deployment import AgentDeploymentService
28
- from claude_mpm.services.ticket_manager import TicketManager
29
44
  from claude_mpm.services.hook_service import HookService
30
45
 
31
46
 
32
47
  class ClaudeRunner:
33
48
  """
34
49
  Claude runner that replaces the entire orchestrator system.
35
-
50
+
36
51
  This does exactly what we need:
37
52
  1. Deploy native agents to .claude/agents/
38
53
  2. Run Claude CLI with either exec or subprocess
39
54
  3. Extract tickets if needed
40
55
  4. Handle both interactive and non-interactive modes
41
-
56
+
42
57
  Supports two launch methods:
43
58
  - exec: Replace current process (default for backward compatibility)
44
59
  - subprocess: Launch as child process for more control
45
60
  """
46
-
61
+
47
62
  def __init__(
48
63
  self,
49
64
  enable_tickets: bool = True,
@@ -51,171 +66,193 @@ class ClaudeRunner:
51
66
  claude_args: Optional[list] = None,
52
67
  launch_method: str = "exec", # "exec" or "subprocess"
53
68
  enable_websocket: bool = False,
54
- websocket_port: int = 8765
69
+ websocket_port: int = 8765,
55
70
  ):
56
71
  """Initialize the Claude runner."""
57
- self.enable_tickets = enable_tickets
58
- self.log_level = log_level
59
72
  self.logger = get_logger(__name__)
60
- self.claude_args = claude_args or []
61
- self.launch_method = launch_method
62
- self.enable_websocket = enable_websocket
63
- self.websocket_port = websocket_port
64
-
65
- # Initialize project logger for session logging
66
- self.project_logger = None
67
- if log_level != "OFF":
68
- try:
69
- self.project_logger = get_project_logger(log_level)
70
- self.project_logger.log_system(
71
- f"Initializing ClaudeRunner with {launch_method} launcher",
72
- level="INFO",
73
- component="runner"
74
- )
75
- except ImportError as e:
76
- self.logger.warning(f"Project logger module not available: {e}")
77
- except Exception as e:
78
- self.logger.warning(f"Failed to initialize project logger: {e}")
79
-
80
- # Initialize services using dependency injection
81
- # Determine the user's working directory from environment
82
- user_working_dir = None
83
- if 'CLAUDE_MPM_USER_PWD' in os.environ:
84
- user_working_dir = Path(os.environ['CLAUDE_MPM_USER_PWD'])
85
- self.logger.info(f"Using user working directory from CLAUDE_MPM_USER_PWD", extra={"directory": str(user_working_dir)})
86
-
87
- # Get DI container and resolve services
73
+
74
+ # Initialize configuration service
88
75
  container = get_container()
89
-
90
- # Register and resolve deployment service
91
- if not container.is_registered(AgentDeploymentInterface):
92
- # Lazy import to avoid circular dependencies
93
- from claude_mpm.services.agents.deployment import AgentDeploymentService
94
- container.register_factory(
95
- AgentDeploymentInterface,
96
- lambda c: AgentDeploymentService(working_directory=user_working_dir),
97
- lifetime=ServiceLifetime.SINGLETON
76
+ if not container.is_registered(RunnerConfigurationInterface):
77
+ from claude_mpm.services.runner_configuration_service import (
78
+ RunnerConfigurationService,
98
79
  )
99
-
100
- try:
101
- self.deployment_service = container.get(AgentDeploymentInterface)
102
- except Exception as e:
103
- self.logger.error(f"Failed to resolve AgentDeploymentService", exc_info=True)
104
- raise RuntimeError(f"Agent deployment service initialization failed: {e}") from e
105
-
106
- # Initialize ticket manager if enabled using DI
107
- if enable_tickets:
108
- if not container.is_registered(TicketManagerInterface):
109
- # Lazy import to avoid circular dependencies
110
- from claude_mpm.services.ticket_manager import TicketManager
111
- container.register_singleton(TicketManagerInterface, TicketManager)
112
-
113
- try:
114
- self.ticket_manager = container.get(TicketManagerInterface)
115
- except Exception as e:
116
- self.logger.warning("Failed to initialize TicketManager", exc_info=True)
117
- self.ticket_manager = None
118
- self.enable_tickets = False
119
- else:
120
- self.ticket_manager = None
121
-
122
- # Initialize configuration
80
+
81
+ container.register_singleton(
82
+ RunnerConfigurationInterface, RunnerConfigurationService
83
+ )
84
+
123
85
  try:
124
- self.config = Config()
125
- except FileNotFoundError as e:
126
- self.logger.warning("Configuration file not found, using defaults", extra={"error": str(e)})
127
- self.config = Config() # Will use defaults
86
+ self.configuration_service = container.get(RunnerConfigurationInterface)
128
87
  except Exception as e:
129
- self.logger.error("Failed to load configuration", exc_info=True)
130
- raise RuntimeError(f"Configuration initialization failed: {e}") from e
131
-
132
- # Initialize response logging if enabled
133
- self.response_logger = None
134
- response_config = self.config.get('response_logging', {})
135
- if response_config.get('enabled', False):
136
- try:
137
- from claude_mpm.services.claude_session_logger import get_session_logger
138
- self.response_logger = get_session_logger(self.config)
139
- if self.project_logger:
140
- self.project_logger.log_system(
141
- "Response logging initialized",
142
- level="INFO",
143
- component="logging"
144
- )
145
- except Exception as e:
146
- self.logger.warning("Failed to initialize response logger", exc_info=True)
147
-
148
- # Initialize hook service using DI
149
- if not container.is_registered(HookServiceInterface):
150
- # Lazy import to avoid circular dependencies
151
- from claude_mpm.services.hook_service import HookService
152
- container.register_factory(
153
- HookServiceInterface,
154
- lambda c: HookService(self.config),
155
- lifetime=ServiceLifetime.SINGLETON
88
+ self.logger.error(
89
+ "Failed to initialize configuration service", exc_info=True
156
90
  )
157
-
91
+ raise RuntimeError(
92
+ f"Configuration service initialization failed: {e}"
93
+ ) from e
94
+
95
+ # Initialize configuration using the service
96
+ config_data = self.configuration_service.initialize_configuration(
97
+ enable_tickets=enable_tickets,
98
+ log_level=log_level,
99
+ claude_args=claude_args,
100
+ launch_method=launch_method,
101
+ enable_websocket=enable_websocket,
102
+ websocket_port=websocket_port,
103
+ )
104
+
105
+ # Set configuration attributes
106
+ self.enable_tickets = config_data["enable_tickets"]
107
+ self.log_level = config_data["log_level"]
108
+ self.claude_args = config_data["claude_args"]
109
+ self.launch_method = config_data["launch_method"]
110
+ self.enable_websocket = config_data["enable_websocket"]
111
+ self.websocket_port = config_data["websocket_port"]
112
+ self.config = config_data["config"]
113
+
114
+ # Initialize project logger using the service
115
+ self.project_logger = self.configuration_service.initialize_project_logger(
116
+ self.log_level
117
+ )
118
+
119
+ # Initialize services using dependency injection and configuration service
120
+ user_working_dir = self.configuration_service.get_user_working_directory()
121
+
122
+ # Register core services
123
+ self.configuration_service.register_core_services(container, user_working_dir)
124
+
158
125
  try:
159
- self.hook_service = container.get(HookServiceInterface)
160
- self._register_memory_hooks()
126
+ self.deployment_service = container.get(AgentDeploymentInterface)
161
127
  except Exception as e:
162
- self.logger.warning("Failed to initialize hook service", exc_info=True)
163
- self.hook_service = None
164
-
165
- # Load system instructions
166
- self.system_instructions = self._load_system_instructions()
167
-
168
- # Track if we need to create session logs
169
- self.session_log_file = None
170
- if self.project_logger and log_level != "OFF":
171
- try:
172
- # Create a system.jsonl file in the session directory
173
- self.session_log_file = self.project_logger.session_dir / "system.jsonl"
174
- self._log_session_event({
128
+ self.logger.error(
129
+ f"Failed to resolve AgentDeploymentService", exc_info=True
130
+ )
131
+ raise RuntimeError(
132
+ f"Agent deployment service initialization failed: {e}"
133
+ ) from e
134
+
135
+ # Ticket manager disabled - use claude-mpm tickets CLI commands instead
136
+ self.ticket_manager = None
137
+ self.enable_tickets = False
138
+
139
+ # Initialize response logger using configuration service
140
+ self.response_logger = self.configuration_service.initialize_response_logger(
141
+ self.config, self.project_logger
142
+ )
143
+
144
+ # Initialize hook service using configuration service
145
+ self.hook_service = self.configuration_service.register_hook_service(
146
+ container, self.config
147
+ )
148
+
149
+ # Initialize memory hook service using configuration service
150
+ self.memory_hook_service = (
151
+ self.configuration_service.register_memory_hook_service(
152
+ container, self.hook_service
153
+ )
154
+ )
155
+ if self.memory_hook_service:
156
+ self.memory_hook_service.register_memory_hooks()
157
+
158
+ # Initialize agent capabilities service using configuration service
159
+ self.agent_capabilities_service = (
160
+ self.configuration_service.register_agent_capabilities_service(container)
161
+ )
162
+
163
+ # Initialize system instructions service using configuration service
164
+ self.system_instructions_service = (
165
+ self.configuration_service.register_system_instructions_service(
166
+ container, self.agent_capabilities_service
167
+ )
168
+ )
169
+
170
+ # Initialize Socket.IO server reference first
171
+ self.websocket_server = None
172
+
173
+ # Initialize subprocess launcher service using configuration service
174
+ self.subprocess_launcher_service = (
175
+ self.configuration_service.register_subprocess_launcher_service(
176
+ container, self.project_logger, self.websocket_server
177
+ )
178
+ )
179
+
180
+ # Initialize version service using configuration service
181
+ self.version_service = self.configuration_service.register_version_service(
182
+ container
183
+ )
184
+
185
+ # Initialize command handler service using configuration service
186
+ self.command_handler_service = (
187
+ self.configuration_service.register_command_handler_service(
188
+ container, self.project_logger
189
+ )
190
+ )
191
+
192
+ # Initialize session management service using configuration service
193
+ # Note: This must be done after other services are initialized since it depends on the runner
194
+ self.session_management_service = (
195
+ self.configuration_service.register_session_management_service(
196
+ container, self
197
+ )
198
+ )
199
+
200
+ # Initialize utility service using configuration service
201
+ self.utility_service = self.configuration_service.register_utility_service(
202
+ container
203
+ )
204
+
205
+ # Load system instructions using the service
206
+ if self.system_instructions_service:
207
+ self.system_instructions = (
208
+ self.system_instructions_service.load_system_instructions()
209
+ )
210
+ else:
211
+ self.system_instructions = self._load_system_instructions()
212
+
213
+ # Create session log file using configuration service
214
+ self.session_log_file = self.configuration_service.create_session_log_file(
215
+ self.project_logger, self.log_level, config_data
216
+ )
217
+
218
+ # Log session start event if we have a session log file
219
+ if self.session_log_file:
220
+ self._log_session_event(
221
+ {
175
222
  "event": "session_start",
176
223
  "runner": "ClaudeRunner",
177
- "enable_tickets": enable_tickets,
178
- "log_level": log_level,
179
- "launch_method": launch_method
180
- })
181
- except PermissionError as e:
182
- self.logger.debug(f"Permission denied creating session log file: {e}")
183
- except OSError as e:
184
- self.logger.debug(f"OS error creating session log file: {e}")
185
- except Exception as e:
186
- self.logger.debug(f"Failed to create session log file: {e}")
187
-
188
- # Initialize Socket.IO server reference
189
- self.websocket_server = None
190
-
224
+ "enable_tickets": self.enable_tickets,
225
+ "log_level": self.log_level,
226
+ "launch_method": self.launch_method,
227
+ }
228
+ )
229
+
191
230
  def setup_agents(self) -> bool:
192
231
  """Deploy native agents to .claude/agents/."""
193
232
  try:
194
233
  if self.project_logger:
195
234
  self.project_logger.log_system(
196
- "Starting agent deployment",
197
- level="INFO",
198
- component="deployment"
235
+ "Starting agent deployment", level="INFO", component="deployment"
199
236
  )
200
-
237
+
201
238
  results = self.deployment_service.deploy_agents()
202
-
239
+
203
240
  if results["deployed"] or results.get("updated", []):
204
- deployed_count = len(results['deployed'])
205
- updated_count = len(results.get('updated', []))
206
-
241
+ deployed_count = len(results["deployed"])
242
+ updated_count = len(results.get("updated", []))
243
+
207
244
  if deployed_count > 0:
208
245
  print(f"✓ Deployed {deployed_count} native agents")
209
246
  if updated_count > 0:
210
247
  print(f"✓ Updated {updated_count} agents")
211
-
248
+
212
249
  if self.project_logger:
213
250
  self.project_logger.log_system(
214
251
  f"Agent deployment successful: {deployed_count} deployed, {updated_count} updated",
215
252
  level="INFO",
216
- component="deployment"
253
+ component="deployment",
217
254
  )
218
-
255
+
219
256
  # Set Claude environment
220
257
  self.deployment_service.set_claude_environment()
221
258
  return True
@@ -225,77 +262,86 @@ class ClaudeRunner:
225
262
  self.project_logger.log_system(
226
263
  "All agents already up to date",
227
264
  level="INFO",
228
- component="deployment"
265
+ component="deployment",
229
266
  )
230
267
  return True
231
-
232
-
268
+
233
269
  except PermissionError as e:
234
270
  error_msg = f"Permission denied deploying agents to .claude/agents/: {e}"
235
271
  self.logger.error(error_msg)
236
272
  print(f"❌ {error_msg}")
237
- print("💡 Try running with appropriate permissions or check directory ownership")
273
+ print(
274
+ "💡 Try running with appropriate permissions or check directory ownership"
275
+ )
238
276
  if self.project_logger:
239
- self.project_logger.log_system(error_msg, level="ERROR", component="deployment")
277
+ self.project_logger.log_system(
278
+ error_msg, level="ERROR", component="deployment"
279
+ )
240
280
  return False
241
-
281
+
242
282
  except FileNotFoundError as e:
243
283
  error_msg = f"Agent files not found: {e}"
244
284
  self.logger.error(error_msg)
245
285
  print(f"❌ {error_msg}")
246
286
  print("💡 Ensure claude-mpm is properly installed")
247
287
  if self.project_logger:
248
- self.project_logger.log_system(error_msg, level="ERROR", component="deployment")
288
+ self.project_logger.log_system(
289
+ error_msg, level="ERROR", component="deployment"
290
+ )
249
291
  return False
250
-
292
+
251
293
  except ImportError as e:
252
294
  error_msg = f"Missing required module for agent deployment: {e}"
253
295
  self.logger.error(error_msg)
254
296
  print(f"⚠️ {error_msg}")
255
297
  print("💡 Some agent features may be limited")
256
298
  if self.project_logger:
257
- self.project_logger.log_system(error_msg, level="WARNING", component="deployment")
299
+ self.project_logger.log_system(
300
+ error_msg, level="WARNING", component="deployment"
301
+ )
258
302
  return False
259
-
303
+
260
304
  except Exception as e:
261
305
  error_msg = f"Unexpected error during agent deployment: {e}"
262
306
  self.logger.error(error_msg)
263
307
  print(f"⚠️ {error_msg}")
264
308
  if self.project_logger:
265
- self.project_logger.log_system(error_msg, level="ERROR", component="deployment")
309
+ self.project_logger.log_system(
310
+ error_msg, level="ERROR", component="deployment"
311
+ )
266
312
  # Continue without agents rather than failing completely
267
313
  return False
268
-
314
+
269
315
  def ensure_project_agents(self) -> bool:
270
316
  """Ensure system agents are available in the project directory.
271
-
317
+
272
318
  Deploys system agents to project's .claude/agents/ directory
273
319
  if they don't exist or are outdated. This ensures agents are
274
320
  available for Claude Code to use. Project-specific JSON templates
275
321
  should be placed in .claude-mpm/agents/.
276
-
322
+
277
323
  Returns:
278
324
  bool: True if agents are available, False on error
279
325
  """
280
326
  try:
281
327
  # Use the correct user directory, not the framework directory
282
- if 'CLAUDE_MPM_USER_PWD' in os.environ:
283
- project_dir = Path(os.environ['CLAUDE_MPM_USER_PWD'])
328
+ if "CLAUDE_MPM_USER_PWD" in os.environ:
329
+ project_dir = Path(os.environ["CLAUDE_MPM_USER_PWD"])
284
330
  else:
285
331
  project_dir = Path.cwd()
286
-
332
+
287
333
  project_agents_dir = project_dir / ".claude-mpm" / "agents"
288
-
334
+
289
335
  # Create directory if it doesn't exist
290
336
  project_agents_dir.mkdir(parents=True, exist_ok=True)
291
-
337
+
292
338
  if self.project_logger:
293
339
  self.project_logger.log_system(
294
340
  f"Ensuring agents are available in project: {project_agents_dir}",
295
341
  level="INFO",
296
- component="deployment"
342
+ component="deployment",
297
343
  )
298
-
344
+
299
345
  # Deploy agents to project's .claude/agents directory (not .claude-mpm)
300
346
  # This ensures all system agents are deployed regardless of version
301
347
  # .claude-mpm/agents/ should only contain JSON source templates
@@ -303,650 +349,313 @@ class ClaudeRunner:
303
349
  results = self.deployment_service.deploy_agents(
304
350
  target_dir=project_dir / ".claude",
305
351
  force_rebuild=False,
306
- deployment_mode="project"
352
+ deployment_mode="project",
307
353
  )
308
-
354
+
309
355
  if results["deployed"] or results.get("updated", []):
310
- deployed_count = len(results['deployed'])
311
- updated_count = len(results.get('updated', []))
312
-
356
+ deployed_count = len(results["deployed"])
357
+ updated_count = len(results.get("updated", []))
358
+
313
359
  if deployed_count > 0:
314
360
  self.logger.info(f"Deployed {deployed_count} agents to project")
315
361
  if updated_count > 0:
316
362
  self.logger.info(f"Updated {updated_count} agents in project")
317
-
363
+
318
364
  return True
319
365
  elif results.get("skipped", []):
320
366
  # Agents already exist and are current
321
- self.logger.debug(f"Project agents up to date: {len(results['skipped'])} agents")
367
+ self.logger.debug(
368
+ f"Project agents up to date: {len(results['skipped'])} agents"
369
+ )
322
370
  return True
323
371
  else:
324
372
  self.logger.warning("No agents deployed to project")
325
373
  return False
326
-
374
+
327
375
  except Exception as e:
328
376
  self.logger.error(f"Failed to ensure project agents: {e}")
329
377
  if self.project_logger:
330
378
  self.project_logger.log_system(
331
379
  f"Failed to ensure project agents: {e}",
332
380
  level="ERROR",
333
- component="deployment"
381
+ component="deployment",
334
382
  )
335
383
  return False
336
-
384
+
337
385
  def deploy_project_agents_to_claude(self) -> bool:
338
386
  """Deploy project agents from .claude-mpm/agents/ to .claude/agents/.
339
-
387
+
340
388
  This method handles the deployment of project-specific agents (JSON format)
341
389
  from the project's agents directory to Claude's agent directory.
342
390
  Project agents take precedence over system agents.
343
-
391
+
344
392
  WHY: Project agents allow teams to define custom, project-specific agents
345
- that override system agents. These are stored in JSON format in
393
+ that override system agents. These are stored in JSON format in
346
394
  .claude-mpm/agents/ and need to be deployed to .claude/agents/
347
395
  as MD files for Claude to use them.
348
-
396
+
349
397
  Returns:
350
398
  bool: True if deployment successful or no agents to deploy, False on error
351
399
  """
352
400
  try:
353
401
  # Use the correct user directory, not the framework directory
354
- if 'CLAUDE_MPM_USER_PWD' in os.environ:
355
- project_dir = Path(os.environ['CLAUDE_MPM_USER_PWD'])
402
+ if "CLAUDE_MPM_USER_PWD" in os.environ:
403
+ project_dir = Path(os.environ["CLAUDE_MPM_USER_PWD"])
356
404
  else:
357
405
  project_dir = Path.cwd()
358
-
406
+
359
407
  project_agents_dir = project_dir / ".claude-mpm" / "agents"
360
408
  claude_agents_dir = project_dir / ".claude" / "agents"
361
-
409
+
362
410
  # Check if project agents directory exists
363
411
  if not project_agents_dir.exists():
364
412
  self.logger.debug("No project agents directory found")
365
413
  return True # Not an error - just no project agents
366
-
414
+
367
415
  # Get JSON agent files from agents directory
368
416
  json_files = list(project_agents_dir.glob("*.json"))
369
417
  if not json_files:
370
418
  self.logger.debug("No JSON agents in project")
371
419
  return True
372
-
420
+
373
421
  # Create .claude/agents directory if needed
374
422
  claude_agents_dir.mkdir(parents=True, exist_ok=True)
375
-
376
- self.logger.info(f"Deploying {len(json_files)} project agents to .claude/agents/")
423
+
424
+ self.logger.info(
425
+ f"Deploying {len(json_files)} project agents to .claude/agents/"
426
+ )
377
427
  if self.project_logger:
378
428
  self.project_logger.log_system(
379
429
  f"Deploying project agents from {project_agents_dir} to {claude_agents_dir}",
380
430
  level="INFO",
381
- component="deployment"
431
+ component="deployment",
382
432
  )
383
-
433
+
384
434
  deployed_count = 0
385
435
  updated_count = 0
386
436
  errors = []
387
-
437
+
388
438
  # Deploy each JSON agent
389
439
  # CRITICAL: PM (Project Manager) must NEVER be deployed as it's the main Claude instance
390
- EXCLUDED_AGENTS = {'pm', 'project_manager'}
391
-
440
+ EXCLUDED_AGENTS = {"pm", "project_manager"}
441
+
392
442
  # Initialize deployment service with proper base agent path
393
443
  # Use the existing deployment service's base agent path if available
394
444
  base_agent_path = project_agents_dir / "base_agent.json"
395
445
  if not base_agent_path.exists():
396
446
  # Fall back to system base agent
397
447
  base_agent_path = self.deployment_service.base_agent_path
398
-
448
+
399
449
  # Lazy import to avoid circular dependencies
400
450
  from claude_mpm.services.agents.deployment import AgentDeploymentService
401
-
451
+
402
452
  # Create a single deployment service instance for all agents
403
453
  project_deployment = AgentDeploymentService(
404
454
  templates_dir=project_agents_dir,
405
455
  base_agent_path=base_agent_path,
406
- working_directory=project_dir # Pass the project directory
456
+ working_directory=project_dir, # Pass the project directory
407
457
  )
408
-
458
+
409
459
  # Load base agent data once
410
460
  base_agent_data = {}
411
461
  if base_agent_path and base_agent_path.exists():
412
462
  try:
413
463
  import json
464
+
414
465
  base_agent_data = json.loads(base_agent_path.read_text())
415
466
  except Exception as e:
416
467
  self.logger.warning(f"Could not load base agent: {e}")
417
-
468
+
418
469
  for json_file in json_files:
419
470
  try:
420
471
  agent_name = json_file.stem
421
-
472
+
422
473
  # Skip PM agent - it's the main Claude instance, not a subagent
423
474
  if agent_name.lower() in EXCLUDED_AGENTS:
424
- self.logger.info(f"Skipping {agent_name} (PM is the main Claude instance)")
475
+ self.logger.info(
476
+ f"Skipping {agent_name} (PM is the main Claude instance)"
477
+ )
425
478
  continue
426
-
479
+
427
480
  target_file = claude_agents_dir / f"{agent_name}.md"
428
-
481
+
429
482
  # Check if agent needs update
430
483
  needs_update = True
431
484
  if target_file.exists():
432
485
  # Check if it's a project agent (has project marker)
433
486
  existing_content = target_file.read_text()
434
- if "author: claude-mpm-project" in existing_content or "source: project" in existing_content:
487
+ if (
488
+ "author: claude-mpm-project" in existing_content
489
+ or "source: project" in existing_content
490
+ ):
435
491
  # Compare modification times
436
492
  if target_file.stat().st_mtime >= json_file.stat().st_mtime:
437
493
  needs_update = False
438
- self.logger.debug(f"Project agent {agent_name} is up to date")
439
-
494
+ self.logger.debug(
495
+ f"Project agent {agent_name} is up to date"
496
+ )
497
+
440
498
  if needs_update:
441
499
  # Build the agent markdown using the pre-initialized service and base agent data
442
500
  agent_content = project_deployment._build_agent_markdown(
443
501
  agent_name, json_file, base_agent_data
444
502
  )
445
-
503
+
446
504
  # Mark as project agent
447
505
  agent_content = agent_content.replace(
448
- "author: claude-mpm",
449
- "author: claude-mpm-project"
506
+ "author: claude-mpm", "author: claude-mpm-project"
450
507
  )
451
-
508
+
452
509
  # Write the agent file
453
510
  is_update = target_file.exists()
454
511
  target_file.write_text(agent_content)
455
-
512
+
456
513
  if is_update:
457
514
  updated_count += 1
458
515
  self.logger.info(f"Updated project agent: {agent_name}")
459
516
  else:
460
517
  deployed_count += 1
461
518
  self.logger.info(f"Deployed project agent: {agent_name}")
462
-
519
+
463
520
  except Exception as e:
464
521
  error_msg = f"Failed to deploy project agent {json_file.name}: {e}"
465
522
  self.logger.error(error_msg)
466
523
  errors.append(error_msg)
467
-
524
+
468
525
  # Report results
469
526
  if deployed_count > 0 or updated_count > 0:
470
- print(f"✓ Deployed {deployed_count} project agents, updated {updated_count}")
527
+ print(
528
+ f"✓ Deployed {deployed_count} project agents, updated {updated_count}"
529
+ )
471
530
  if self.project_logger:
472
531
  self.project_logger.log_system(
473
532
  f"Project agent deployment: {deployed_count} deployed, {updated_count} updated",
474
533
  level="INFO",
475
- component="deployment"
534
+ component="deployment",
476
535
  )
477
-
536
+
478
537
  if errors:
479
538
  for error in errors:
480
539
  print(f"⚠️ {error}")
481
540
  return False
482
-
541
+
483
542
  return True
484
-
543
+
485
544
  except Exception as e:
486
545
  error_msg = f"Failed to deploy project agents: {e}"
487
546
  self.logger.error(error_msg)
488
547
  print(f"⚠️ {error_msg}")
489
548
  if self.project_logger:
490
- self.project_logger.log_system(error_msg, level="ERROR", component="deployment")
549
+ self.project_logger.log_system(
550
+ error_msg, level="ERROR", component="deployment"
551
+ )
491
552
  return False
492
-
553
+
493
554
  def run_interactive(self, initial_context: Optional[str] = None):
494
- """Run Claude in interactive mode.
495
-
496
- WHY: This method now delegates to InteractiveSession class for better
497
- maintainability and reduced complexity. The session class handles all
498
- the details while this method provides the simple interface.
499
-
500
- DESIGN DECISION: Using delegation pattern to reduce complexity from
501
- 39 to <10 and lines from 262 to <80, while maintaining 100% backward
502
- compatibility. All functionality including response logging through
503
- the hook system is preserved.
504
-
505
- The hook system continues to capture Claude events (UserPromptSubmit,
506
- PreToolUse, PostToolUse, Task delegations) directly from Claude Code,
507
- providing comprehensive event capture without process control overhead.
508
-
555
+ """Run Claude in interactive mode using the session management service.
556
+
557
+ Delegates to the SessionManagementService for session orchestration.
558
+
509
559
  Args:
510
560
  initial_context: Optional initial context to pass to Claude
511
561
  """
512
- from claude_mpm.core.interactive_session import InteractiveSession
513
-
514
- # Create session handler
515
- session = InteractiveSession(self)
516
-
517
- try:
518
- # Step 1: Initialize session
519
- success, error = session.initialize_interactive_session()
520
- if not success:
521
- self.logger.error(f"Failed to initialize interactive session: {error}")
522
- return
523
-
524
- # Step 2: Set up environment
525
- success, environment = session.setup_interactive_environment()
526
- if not success:
527
- self.logger.error("Failed to setup interactive environment")
528
- return
529
-
530
- # Step 3: Handle interactive input/output
531
- # This is where the actual Claude process runs
532
- session.handle_interactive_input(environment)
533
-
534
- finally:
535
- # Step 4: Clean up session
536
- session.cleanup_interactive_session()
537
-
562
+ if self.session_management_service:
563
+ self.session_management_service.run_interactive_session(initial_context)
564
+ else:
565
+ self.logger.error("Session management service not available")
566
+ print("Error: Session management service not available")
567
+
538
568
  def run_oneshot(self, prompt: str, context: Optional[str] = None) -> bool:
539
- """Run Claude with a single prompt and return success status.
540
-
541
- WHY: This method now delegates to OneshotSession class for better
542
- maintainability and reduced complexity. The session class handles
543
- all the details while this method provides the simple interface.
544
-
545
- DESIGN DECISION: Using delegation pattern to reduce complexity from
546
- 50 to <10 and lines from 332 to <80, while maintaining 100% backward
547
- compatibility. All functionality is preserved through the session class.
548
-
569
+ """Run Claude with a single prompt using the session management service.
570
+
571
+ Delegates to the SessionManagementService for session orchestration.
572
+
549
573
  Args:
550
574
  prompt: The command or prompt to execute
551
575
  context: Optional context to prepend to the prompt
552
-
576
+
553
577
  Returns:
554
578
  bool: True if successful, False otherwise
555
579
  """
556
- from claude_mpm.core.oneshot_session import OneshotSession
557
-
558
- # Create session handler
559
- session = OneshotSession(self)
560
-
561
- try:
562
- # Step 1: Initialize session
563
- success, error = session.initialize_session(prompt)
564
- if not success:
565
- return False
566
-
567
- # Special case: MPM commands return early
568
- if error is None and prompt.strip().startswith("/mpm:"):
569
- return success
570
-
571
- # Step 2: Deploy agents
572
- if not session.deploy_agents():
573
- self.logger.warning("Agent deployment had issues, continuing...")
574
-
575
- # Step 3: Set up infrastructure
576
- infrastructure = session.setup_infrastructure()
577
-
578
- # Step 4: Execute command
579
- success, response = session.execute_command(prompt, context, infrastructure)
580
-
581
- return success
582
-
583
- finally:
584
- # Step 5: Clean up session
585
- session.cleanup_session()
586
-
580
+ if self.session_management_service:
581
+ return self.session_management_service.run_oneshot_session(prompt, context)
582
+ else:
583
+ self.logger.error("Session management service not available")
584
+ print("Error: Session management service not available")
585
+ return False
586
+
587
587
  def _extract_tickets(self, text: str):
588
- """Extract tickets from Claude's response."""
589
- if not self.ticket_manager:
590
- return
591
-
592
- try:
593
- # Use the ticket manager's extraction logic if available
594
- if hasattr(self.ticket_manager, 'extract_tickets_from_text'):
595
- tickets = self.ticket_manager.extract_tickets_from_text(text)
596
- if tickets:
597
- print(f"\n📋 Extracted {len(tickets)} tickets")
598
- for ticket in tickets[:3]: # Show first 3
599
- print(f" - [{ticket.get('id', 'N/A')}] {ticket.get('title', 'No title')}")
600
- if len(tickets) > 3:
601
- print(f" ... and {len(tickets) - 3} more")
602
- else:
603
- self.logger.debug("Ticket extraction method not available")
604
- except AttributeError as e:
605
- self.logger.debug(f"Ticket manager missing expected method: {e}")
606
- except TypeError as e:
607
- self.logger.debug(f"Invalid ticket data format: {e}")
608
- except Exception as e:
609
- self.logger.debug(f"Unexpected error during ticket extraction: {e}")
588
+ """Extract tickets from Claude's response (disabled - use claude-mpm tickets CLI)."""
589
+ # Ticket extraction disabled - users should use claude-mpm tickets CLI commands
590
+ pass
610
591
 
611
592
  def _load_system_instructions(self) -> Optional[str]:
612
- """Load and process system instructions from agents/INSTRUCTIONS.md.
613
-
614
- Implements project > framework precedence:
615
- 1. First check for project-specific instructions in .claude-mpm/agents/INSTRUCTIONS.md
616
- 2. If not found, fall back to framework instructions in src/claude_mpm/agents/INSTRUCTIONS.md
617
-
618
- WHY: Allows projects to override the default PM instructions with project-specific
619
- guidance, while maintaining backward compatibility with the framework defaults.
620
-
621
- DESIGN DECISION: Using CLAUDE_MPM_USER_PWD environment variable to locate the
622
- correct project directory, ensuring we check the right location even when
623
- claude-mpm is invoked from a different directory.
593
+ """Load and process system instructions.
594
+
595
+ Delegates to the SystemInstructionsService for loading and processing.
624
596
  """
625
- try:
626
- # Determine the user's project directory
627
- if 'CLAUDE_MPM_USER_PWD' in os.environ:
628
- project_dir = Path(os.environ['CLAUDE_MPM_USER_PWD'])
629
- else:
630
- project_dir = Path.cwd()
631
-
632
- # Check for project-specific INSTRUCTIONS.md first
633
- project_instructions_path = project_dir / ".claude-mpm" / "agents" / "INSTRUCTIONS.md"
634
-
635
- instructions_path = None
636
- instructions_source = None
637
-
638
- if project_instructions_path.exists():
639
- instructions_path = project_instructions_path
640
- instructions_source = "PROJECT"
641
- self.logger.info(f"Found project-specific INSTRUCTIONS.md: {instructions_path}")
642
- else:
643
- # Fall back to framework instructions
644
- module_path = Path(__file__).parent.parent
645
- framework_instructions_path = module_path / "agents" / "INSTRUCTIONS.md"
646
-
647
- if framework_instructions_path.exists():
648
- instructions_path = framework_instructions_path
649
- instructions_source = "FRAMEWORK"
650
- self.logger.info(f"Using framework INSTRUCTIONS.md: {instructions_path}")
651
- else:
652
- self.logger.warning(f"No INSTRUCTIONS.md found in project or framework")
653
- return None
654
-
655
- # Read raw instructions
656
- raw_instructions = instructions_path.read_text()
657
-
658
- # Strip HTML metadata comments before processing
659
- raw_instructions = self._strip_metadata_comments(raw_instructions)
660
-
661
- # Process template variables if ContentAssembler is available
662
- try:
663
- from claude_mpm.services.framework_claude_md_generator.content_assembler import ContentAssembler
664
- assembler = ContentAssembler()
665
- processed_instructions = assembler.apply_template_variables(raw_instructions)
666
-
667
- # Append BASE_PM.md framework requirements with dynamic content
668
- base_pm_path = Path(__file__).parent.parent / "agents" / "BASE_PM.md"
669
- if base_pm_path.exists():
670
- base_pm_content = base_pm_path.read_text()
671
-
672
- # Strip metadata comments from BASE_PM.md as well
673
- base_pm_content = self._strip_metadata_comments(base_pm_content)
674
-
675
- # Process BASE_PM.md with dynamic content injection
676
- base_pm_content = self._process_base_pm_content(base_pm_content)
677
-
678
- processed_instructions += f"\n\n{base_pm_content}"
679
- self.logger.info(f"Appended BASE_PM.md with dynamic capabilities from deployed agents")
680
-
681
- self.logger.info(f"Loaded and processed {instructions_source} PM instructions")
682
- return processed_instructions
683
- except ImportError:
684
- self.logger.warning("ContentAssembler not available, using raw instructions")
685
- self.logger.info(f"Loaded {instructions_source} PM instructions (raw)")
686
- return raw_instructions
687
- except Exception as e:
688
- self.logger.warning(f"Failed to process template variables: {e}, using raw instructions")
689
- self.logger.info(f"Loaded {instructions_source} PM instructions (raw, processing failed)")
690
- return raw_instructions
691
-
692
- except Exception as e:
693
- self.logger.error(f"Failed to load system instructions: {e}")
597
+ if self.system_instructions_service:
598
+ return self.system_instructions_service.load_system_instructions()
599
+ else:
600
+ # Fallback if service is not available
601
+ self.logger.warning(
602
+ "System instructions service not available, using basic fallback"
603
+ )
694
604
  return None
695
605
 
696
606
  def _process_base_pm_content(self, base_pm_content: str) -> str:
697
607
  """Process BASE_PM.md content with dynamic injections.
698
-
699
- This method replaces template variables in BASE_PM.md with:
700
- - {{agent-capabilities}}: List of deployed agents from .claude/agents/
701
- - {{current-date}}: Today's date for temporal context
608
+
609
+ Delegates to the SystemInstructionsService for processing.
702
610
  """
703
- from datetime import datetime
704
-
705
- # Replace {{current-date}} with actual date
706
- current_date = datetime.now().strftime('%Y-%m-%d')
707
- base_pm_content = base_pm_content.replace('{{current-date}}', current_date)
708
-
709
- # Replace {{agent-capabilities}} with deployed agents
710
- if '{{agent-capabilities}}' in base_pm_content:
711
- capabilities_section = self._generate_deployed_agent_capabilities()
712
- base_pm_content = base_pm_content.replace('{{agent-capabilities}}', capabilities_section)
713
-
714
- return base_pm_content
715
-
611
+ if self.system_instructions_service:
612
+ return self.system_instructions_service.process_base_pm_content(
613
+ base_pm_content
614
+ )
615
+ else:
616
+ # Fallback if service is not available
617
+ self.logger.warning(
618
+ "System instructions service not available for BASE_PM processing"
619
+ )
620
+ return base_pm_content
621
+
716
622
  def _strip_metadata_comments(self, content: str) -> str:
717
623
  """Strip HTML metadata comments from content.
718
-
719
- Removes comments like:
720
- <!-- FRAMEWORK_VERSION: 0010 -->
721
- <!-- LAST_MODIFIED: 2025-08-10T00:00:00Z -->
722
- <!-- WORKFLOW_VERSION: ... -->
723
- <!-- PROJECT_WORKFLOW_VERSION: ... -->
724
- <!-- CUSTOM_PROJECT_WORKFLOW -->
725
-
726
- WHY: These metadata comments are useful for internal tracking but should not
727
- appear in the final instructions passed to Claude via --append-system-prompt.
728
- They clutter the instructions and provide no value to the Claude agent.
729
-
730
- DESIGN DECISION: Using regex to remove all HTML comments that contain known
731
- metadata patterns. Also removes any resulting leading blank lines.
624
+
625
+ Delegates to the SystemInstructionsService for processing.
732
626
  """
733
- import re
734
-
735
- # Remove HTML comments that contain metadata
736
- patterns_to_strip = [
737
- 'FRAMEWORK_VERSION',
738
- 'LAST_MODIFIED',
739
- 'WORKFLOW_VERSION',
740
- 'PROJECT_WORKFLOW_VERSION',
741
- 'CUSTOM_PROJECT_WORKFLOW',
742
- 'AGENT_VERSION',
743
- 'METADATA_VERSION'
744
- ]
745
-
746
- # Build regex pattern to match any of these metadata comments
747
- pattern = r'<!--\s*(' + '|'.join(patterns_to_strip) + r')[^>]*-->\n?'
748
- cleaned = re.sub(pattern, '', content)
749
-
750
- # Also remove any leading blank lines that might result
751
- cleaned = cleaned.lstrip('\n')
752
-
753
- return cleaned
754
-
627
+ if self.system_instructions_service:
628
+ return self.system_instructions_service.strip_metadata_comments(content)
629
+ else:
630
+ # Fallback if service is not available
631
+ self.logger.warning(
632
+ "System instructions service not available for metadata stripping"
633
+ )
634
+ return content
635
+
755
636
  def _generate_deployed_agent_capabilities(self) -> str:
756
- """Generate agent capabilities from deployed agents following Claude Code's hierarchy.
757
-
758
- Follows the agent precedence order:
759
- 1. Project agents (.claude/agents/) - highest priority
760
- 2. User agents (~/.config/claude/agents/) - middle priority
761
- 3. System agents (claude-desktop installation) - lowest priority
762
-
763
- Project agents override user/system agents with the same ID.
764
- """
765
- try:
766
- # Track discovered agents by ID to handle overrides
767
- discovered_agents = {}
768
-
769
- # 1. First read system agents (lowest priority)
770
- system_agents_dirs = [
771
- Path.home() / "Library" / "Application Support" / "Claude" / "agents", # macOS
772
- Path.home() / ".config" / "claude" / "agents", # Linux
773
- Path.home() / "AppData" / "Roaming" / "Claude" / "agents", # Windows
774
- ]
775
-
776
- for system_dir in system_agents_dirs:
777
- if system_dir.exists():
778
- self._discover_agents_from_dir(system_dir, discovered_agents, "system")
779
- break
780
-
781
- # 2. Then read user agents (middle priority, overrides system)
782
- user_agents_dir = Path.home() / ".config" / "claude" / "agents"
783
- if user_agents_dir.exists():
784
- self._discover_agents_from_dir(user_agents_dir, discovered_agents, "user")
785
-
786
- # 3. Finally read project agents (highest priority, overrides all)
787
- project_agents_dir = Path.cwd() / ".claude" / "agents"
788
- if project_agents_dir.exists():
789
- self._discover_agents_from_dir(project_agents_dir, discovered_agents, "project")
790
-
791
- if not discovered_agents:
792
- self.logger.warning("No agents found in any tier")
793
- return self._get_fallback_capabilities()
794
-
795
- # Build capabilities section from discovered agents
796
- section = "\n## Available Agent Capabilities\n\n"
797
- section += "You have the following specialized agents available for delegation:\n\n"
798
-
799
- # Group agents by category
800
- agents_by_category = {}
801
- for agent_id, agent_info in discovered_agents.items():
802
- category = agent_info['category']
803
- if category not in agents_by_category:
804
- agents_by_category[category] = []
805
- agents_by_category[category].append(agent_info)
806
-
807
- # Output agents by category
808
- for category in sorted(agents_by_category.keys()):
809
- section += f"\n### {category} Agents\n"
810
- for agent in sorted(agents_by_category[category], key=lambda x: x['name']):
811
- tier_indicator = f" [{agent['tier']}]" if agent['tier'] != 'project' else ""
812
- section += f"- **{agent['name']}** (`{agent['id']}`{tier_indicator}): {agent['description']}\n"
813
-
814
- # Add summary
815
- section += f"\n**Total Available Agents**: {len(discovered_agents)}\n"
816
-
817
- # Show tier distribution
818
- tier_counts = {}
819
- for agent in discovered_agents.values():
820
- tier = agent['tier']
821
- tier_counts[tier] = tier_counts.get(tier, 0) + 1
822
-
823
- if len(tier_counts) > 1:
824
- section += f"**Agent Sources**: "
825
- tier_summary = []
826
- for tier in ['project', 'user', 'system']:
827
- if tier in tier_counts:
828
- tier_summary.append(f"{tier_counts[tier]} {tier}")
829
- section += ", ".join(tier_summary) + "\n"
830
-
831
- section += "Use the agent ID in parentheses when delegating tasks via the Task tool.\n"
832
-
833
- self.logger.info(f"Generated capabilities for {len(discovered_agents)} agents " +
834
- f"(project: {tier_counts.get('project', 0)}, " +
835
- f"user: {tier_counts.get('user', 0)}, " +
836
- f"system: {tier_counts.get('system', 0)})")
837
- return section
838
-
839
- except Exception as e:
840
- self.logger.error(f"Failed to generate deployed agent capabilities: {e}")
841
- return self._get_fallback_capabilities()
842
-
843
- def _discover_agents_from_dir(self, agents_dir: Path, discovered_agents: dict, tier: str):
844
- """Discover agents from a specific directory and add/override in discovered_agents.
845
-
846
- Args:
847
- agents_dir: Directory to search for agent .md files
848
- discovered_agents: Dictionary to update with discovered agents
849
- tier: The tier this directory represents (system/user/project)
637
+ """Generate agent capabilities from deployed agents.
638
+
639
+ Delegates to the AgentCapabilitiesService for agent discovery and formatting.
850
640
  """
851
- if not agents_dir.exists():
852
- return
853
-
854
- agent_files = list(agents_dir.glob("*.md"))
855
- for agent_file in sorted(agent_files):
856
- agent_id = agent_file.stem
857
-
858
- # Skip pm.md if it exists (PM is not a deployable agent)
859
- if agent_id.lower() == 'pm':
860
- continue
861
-
862
- # Read agent content and extract metadata
863
- try:
864
- content = agent_file.read_text()
865
- import re
866
-
867
- # Check for YAML frontmatter
868
- name = agent_id.replace('_', ' ').title()
869
- desc = "Specialized agent for delegation"
870
-
871
- if content.startswith('---'):
872
- # Parse YAML frontmatter
873
- frontmatter_match = re.match(r'^---\n(.*?)\n---', content, re.DOTALL)
874
- if frontmatter_match:
875
- frontmatter = frontmatter_match.group(1)
876
- # Extract name from frontmatter
877
- name_fm_match = re.search(r'^name:\s*(.+)$', frontmatter, re.MULTILINE)
878
- if name_fm_match:
879
- name_value = name_fm_match.group(1).strip()
880
- # Format the name nicely
881
- name = name_value.replace('_', ' ').title()
882
-
883
- # Extract description from frontmatter
884
- desc_fm_match = re.search(r'^description:\s*(.+)$', frontmatter, re.MULTILINE)
885
- if desc_fm_match:
886
- desc = desc_fm_match.group(1).strip()
887
- else:
888
- # No frontmatter, extract from content
889
- name_match = re.search(r'^#\s+(.+?)(?:\s+Agent)?$', content, re.MULTILINE)
890
- if name_match:
891
- name = name_match.group(1)
892
-
893
- # Get first non-heading line after the title
894
- lines = content.split('\n')
895
- for i, line in enumerate(lines):
896
- if line.startswith('#'):
897
- # Found title, look for description after it
898
- for desc_line in lines[i+1:]:
899
- desc_line = desc_line.strip()
900
- if desc_line and not desc_line.startswith('#'):
901
- desc = desc_line
902
- break
903
- break
904
-
905
- # Categorize based on agent name/type
906
- category = self._categorize_agent(agent_id, content)
907
-
908
- # Add or override agent in discovered_agents
909
- discovered_agents[agent_id] = {
910
- 'id': agent_id,
911
- 'name': name,
912
- 'description': desc[:150] + '...' if len(desc) > 150 else desc,
913
- 'category': category,
914
- 'tier': tier,
915
- 'path': str(agent_file)
916
- }
917
-
918
- self.logger.debug(f"Discovered {tier} agent: {agent_id} from {agent_file}")
919
-
920
- except Exception as e:
921
- self.logger.debug(f"Could not parse agent {agent_file}: {e}")
922
- continue
923
- def _categorize_agent(self, agent_id: str, content: str) -> str:
924
- """Categorize an agent based on its ID and content."""
925
- agent_id_lower = agent_id.lower()
926
- content_lower = content.lower()
927
-
928
- if 'engineer' in agent_id_lower or 'engineering' in content_lower:
929
- return "Engineering"
930
- elif 'research' in agent_id_lower or 'analysis' in content_lower or 'analyzer' in agent_id_lower:
931
- return "Research"
932
- elif 'qa' in agent_id_lower or 'quality' in content_lower or 'test' in agent_id_lower:
933
- return "Quality"
934
- elif 'security' in agent_id_lower or 'security' in content_lower:
935
- return "Security"
936
- elif 'doc' in agent_id_lower or 'documentation' in content_lower:
937
- return "Documentation"
938
- elif 'data' in agent_id_lower:
939
- return "Data"
940
- elif 'ops' in agent_id_lower or 'deploy' in agent_id_lower or 'operations' in content_lower:
941
- return "Operations"
942
- elif 'version' in agent_id_lower or 'git' in content_lower:
943
- return "Version Control"
641
+ if self.agent_capabilities_service:
642
+ return (
643
+ self.agent_capabilities_service.generate_deployed_agent_capabilities()
644
+ )
944
645
  else:
945
- return "General"
946
-
646
+ # Fallback if service is not available
647
+ self.logger.warning(
648
+ "Agent capabilities service not available, using fallback"
649
+ )
650
+ return self._get_fallback_capabilities()
651
+
947
652
  def _get_fallback_capabilities(self) -> str:
948
653
  """Return fallback agent capabilities when deployed agents can't be read."""
949
- return """
654
+ # Delegate to the service if available, otherwise use basic fallback
655
+ if self.agent_capabilities_service:
656
+ return self.agent_capabilities_service._get_fallback_capabilities()
657
+ else:
658
+ return """
950
659
  ## Available Agent Capabilities
951
660
 
952
661
  You have the following specialized agents available for delegation:
@@ -955,490 +664,86 @@ You have the following specialized agents available for delegation:
955
664
  - **Research Agent**: Investigation and analysis
956
665
  - **QA Agent**: Testing and quality assurance
957
666
  - **Documentation Agent**: Documentation creation and maintenance
958
- - **Security Agent**: Security analysis and protection
959
- - **Data Engineer Agent**: Data management and pipelines
960
- - **Ops Agent**: Deployment and operations
961
- - **Version Control Agent**: Git operations and version management
962
667
 
963
668
  Use these agents to delegate specialized work via the Task tool.
964
669
  """
965
-
966
- def _generate_agent_capabilities_section(self, agents: dict) -> str:
967
- """Generate dynamic agent capabilities section from available agents."""
968
- if not agents:
969
- return ""
970
-
971
- # Build capabilities section
972
- section = "\n\n## Available Agent Capabilities\n\n"
973
- section += "You have the following specialized agents available for delegation:\n\n"
974
-
975
- # Group agents by category
976
- categories = {}
977
- for agent_id, info in agents.items():
978
- category = info.get('category', 'general')
979
- if category not in categories:
980
- categories[category] = []
981
- categories[category].append((agent_id, info))
982
-
983
- # List agents by category
984
- for category in sorted(categories.keys()):
985
- section += f"\n### {category.title()} Agents\n"
986
- for agent_id, info in sorted(categories[category]):
987
- name = info.get('name', agent_id)
988
- desc = info.get('description', 'Specialized agent')
989
- tools = info.get('tools', [])
990
- section += f"- **{name}** (`{agent_id}`): {desc}\n"
991
- if tools:
992
- section += f" - Tools: {', '.join(tools[:5])}"
993
- if len(tools) > 5:
994
- section += f" (+{len(tools)-5} more)"
995
- section += "\n"
996
-
997
- # Add summary
998
- section += f"\n**Total Available Agents**: {len(agents)}\n"
999
- section += "Use the agent ID in parentheses when delegating tasks via the Task tool.\n"
1000
-
1001
- return section
1002
-
670
+
1003
671
  def _create_system_prompt(self) -> str:
1004
- """Create the complete system prompt including instructions."""
1005
- if self.system_instructions:
1006
- return self.system_instructions
672
+ """Create the complete system prompt including instructions.
673
+
674
+ Delegates to the SystemInstructionsService for prompt creation.
675
+ """
676
+ if self.system_instructions_service:
677
+ return self.system_instructions_service.create_system_prompt(
678
+ self.system_instructions
679
+ )
1007
680
  else:
1008
- # Fallback to basic context
1009
- return create_simple_context()
1010
-
681
+ # Fallback if service is not available
682
+ if self.system_instructions:
683
+ return self.system_instructions
684
+ else:
685
+ return create_simple_context()
686
+
1011
687
  def _contains_delegation(self, text: str) -> bool:
1012
- """Check if text contains signs of agent delegation."""
1013
- # Look for common delegation patterns
1014
- delegation_patterns = [
1015
- "Task(",
1016
- "subagent_type=",
1017
- "delegating to",
1018
- "asking the",
1019
- "engineer agent",
1020
- "qa agent",
1021
- "documentation agent",
1022
- "research agent",
1023
- "security agent",
1024
- "ops agent",
1025
- "version_control agent",
1026
- "data_engineer agent"
1027
- ]
1028
-
1029
- text_lower = text.lower()
1030
- return any(pattern.lower() in text_lower for pattern in delegation_patterns)
1031
-
688
+ """Check if text contains signs of agent delegation using the utility service."""
689
+ if self.utility_service:
690
+ return self.utility_service.contains_delegation(text)
691
+ else:
692
+ # Fallback if service not available
693
+ return False
694
+
1032
695
  def _extract_agent_from_response(self, text: str) -> Optional[str]:
1033
- """Try to extract agent name from delegation response."""
1034
- # Look for common patterns
1035
- import re
1036
-
1037
- # Pattern 1: subagent_type="agent_name"
1038
- match = re.search(r'subagent_type=["\']([^"\']*)["\'\)]', text)
1039
- if match:
1040
- return match.group(1)
1041
-
1042
- # Pattern 2: "engineer agent" etc
1043
- agent_names = [
1044
- "engineer", "qa", "documentation", "research",
1045
- "security", "ops", "version_control", "data_engineer"
1046
- ]
1047
- text_lower = text.lower()
1048
- for agent in agent_names:
1049
- if f"{agent} agent" in text_lower or f"agent: {agent}" in text_lower:
1050
- return agent
1051
-
1052
- return None
1053
-
696
+ """Try to extract agent name from delegation response using the utility service."""
697
+ if self.utility_service:
698
+ return self.utility_service.extract_agent_from_response(text)
699
+ else:
700
+ # Fallback if service not available
701
+ return None
702
+
1054
703
  def _handle_mpm_command(self, prompt: str) -> bool:
1055
- """Handle /mpm: commands directly without going to Claude."""
1056
- try:
1057
- # Extract command and arguments
1058
- command_line = prompt[5:].strip() # Remove "/mpm:"
1059
- parts = command_line.split()
1060
-
1061
- if not parts:
1062
- print("No command specified. Available commands: test")
1063
- return True
1064
-
1065
- command = parts[0]
1066
- args = parts[1:]
1067
-
1068
- # Handle commands
1069
- if command == "test":
1070
- print("Hello World")
1071
- if self.project_logger:
1072
- self.project_logger.log_system(
1073
- "Executed /mpm:test command",
1074
- level="INFO",
1075
- component="command"
1076
- )
1077
- return True
1078
- elif command == "agents":
1079
- # Handle agents command - display deployed agent versions
1080
- # WHY: This provides users with a quick way to check deployed agent versions
1081
- # directly from within Claude Code, maintaining consistency with CLI behavior
1082
- try:
1083
- from claude_mpm.cli import _get_agent_versions_display
1084
- agent_versions = _get_agent_versions_display()
1085
- if agent_versions:
1086
- print(agent_versions)
1087
- else:
1088
- print("No deployed agents found")
1089
- print("\nTo deploy agents, run: claude-mpm --mpm:agents deploy")
1090
-
1091
- if self.project_logger:
1092
- self.project_logger.log_system(
1093
- "Executed /mpm:agents command",
1094
- level="INFO",
1095
- component="command"
1096
- )
1097
- return True
1098
- except ImportError as e:
1099
- print(f"Error: CLI module not available: {e}")
1100
- return False
1101
- except Exception as e:
1102
- print(f"Error getting agent versions: {e}")
1103
- return False
1104
- else:
1105
- print(f"Unknown command: {command}")
1106
- print("Available commands: test, agents")
1107
- return True
1108
-
1109
- except KeyboardInterrupt:
1110
- print("\nCommand interrupted")
1111
- return False
1112
- except Exception as e:
1113
- print(f"Error executing command: {e}")
1114
- if self.project_logger:
1115
- self.project_logger.log_system(
1116
- f"Failed to execute /mpm: command: {e}",
1117
- level="ERROR",
1118
- component="command"
1119
- )
704
+ """Handle /mpm: commands using the command handler service.
705
+
706
+ Delegates to the CommandHandlerService for command processing.
707
+ """
708
+ if self.command_handler_service:
709
+ return self.command_handler_service.handle_mpm_command(prompt)
710
+ else:
711
+ # Fallback if service not available
712
+ print("Command handler service not available")
1120
713
  return False
1121
-
714
+
1122
715
  def _log_session_event(self, event_data: dict):
1123
- """Log an event to the session log file."""
1124
- if self.session_log_file:
1125
- try:
1126
- log_entry = {
1127
- "timestamp": datetime.now().isoformat(),
1128
- **event_data
1129
- }
1130
-
1131
- with open(self.session_log_file, 'a') as f:
1132
- f.write(json.dumps(log_entry) + '\n')
1133
- except (OSError, IOError) as e:
1134
- self.logger.debug(f"IO error logging session event: {e}")
1135
- except Exception as e:
1136
- self.logger.debug(f"Failed to log session event: {e}")
1137
-
716
+ """Log an event to the session log file using the utility service."""
717
+ if self.utility_service:
718
+ self.utility_service.log_session_event(self.session_log_file, event_data)
719
+ else:
720
+ # Fallback if service not available
721
+ self.logger.debug("Utility service not available for session logging")
722
+
1138
723
  def _get_version(self) -> str:
724
+ """Get version string using the version service.
725
+
726
+ Delegates to the VersionService for version detection and formatting.
1139
727
  """
1140
- Robust version determination with build number tracking.
1141
-
1142
- WHY: The version display is critical for debugging and user experience.
1143
- This implementation ensures we always show the correct version with build
1144
- number for precise tracking of code changes.
1145
-
1146
- DESIGN DECISION: We combine semantic version with build number:
1147
- - Semantic version (X.Y.Z) for API compatibility tracking
1148
- - Build number for fine-grained code change tracking
1149
- - Format: vX.Y.Z-BBBBB (5-digit zero-padded build number)
1150
-
1151
- Returns version string formatted as "vX.Y.Z-BBBBB"
1152
- """
1153
- version = "0.0.0"
1154
- method_used = "default"
1155
- build_number = None
1156
-
1157
- # Method 1: Try package import (fastest, most common)
1158
- try:
1159
- from claude_mpm import __version__
1160
- version = __version__
1161
- method_used = "package_import"
1162
- self.logger.debug(f"Version obtained via package import: {version}")
1163
- # If version already includes build number (PEP 440 format), extract it
1164
- if '+build.' in version:
1165
- parts = version.split('+build.')
1166
- version = parts[0] # Base version without build
1167
- build_number = int(parts[1]) if len(parts) > 1 else None
1168
- self.logger.debug(f"Extracted base version: {version}, build: {build_number}")
1169
- except ImportError as e:
1170
- self.logger.debug(f"Package import failed: {e}")
1171
- except Exception as e:
1172
- self.logger.warning(f"Unexpected error in package import: {e}")
1173
-
1174
- # Method 2: Try importlib.metadata (standard for installed packages)
1175
- if version == "0.0.0":
1176
- try:
1177
- import importlib.metadata
1178
- version = importlib.metadata.version('claude-mpm')
1179
- method_used = "importlib_metadata"
1180
- self.logger.debug(f"Version obtained via importlib.metadata: {version}")
1181
- except importlib.metadata.PackageNotFoundError:
1182
- self.logger.debug("Package not found in importlib.metadata (likely development install)")
1183
- except ImportError:
1184
- self.logger.debug("importlib.metadata not available (Python < 3.8)")
1185
- except Exception as e:
1186
- self.logger.warning(f"Unexpected error in importlib.metadata: {e}")
1187
-
1188
- # Method 3: Try reading VERSION file directly (development fallback)
1189
- if version == "0.0.0":
1190
- try:
1191
- # Use centralized path management for VERSION file
1192
- if paths.version_file.exists():
1193
- version = paths.version_file.read_text().strip()
1194
- method_used = "version_file"
1195
- self.logger.debug(f"Version obtained via VERSION file: {version}")
1196
- else:
1197
- self.logger.debug(f"VERSION file not found at: {paths.version_file}")
1198
- except Exception as e:
1199
- self.logger.warning(f"Failed to read VERSION file: {e}")
1200
-
1201
- # Try to read build number (only if not already obtained from version string)
1202
- if build_number is None:
1203
- try:
1204
- build_file = paths.project_root / "BUILD_NUMBER"
1205
- if build_file.exists():
1206
- build_content = build_file.read_text().strip()
1207
- build_number = int(build_content)
1208
- self.logger.debug(f"Build number obtained from file: {build_number}")
1209
- except (ValueError, IOError) as e:
1210
- self.logger.debug(f"Could not read BUILD_NUMBER: {e}")
1211
- build_number = None
1212
- except Exception as e:
1213
- self.logger.debug(f"Unexpected error reading BUILD_NUMBER: {e}")
1214
- build_number = None
1215
-
1216
- # Log final result
1217
- if version == "0.0.0":
1218
- self.logger.error(
1219
- "All version detection methods failed. This indicates a packaging or installation issue."
1220
- )
1221
- else:
1222
- self.logger.debug(f"Final version: {version} (method: {method_used})")
1223
-
1224
- # Format version with build number if available
1225
- # For development: Use PEP 440 format (e.g., "3.9.5+build.275")
1226
- # For UI/logging: Use dash format (e.g., "v3.9.5-build.275")
1227
- # For PyPI releases: Use clean version (e.g., "3.9.5")
1228
-
1229
- # Determine formatting context (default to UI format for claude_runner)
1230
- if build_number is not None:
1231
- # UI/logging format with 'v' prefix and dash separator
1232
- return f"v{version}-build.{build_number}"
728
+ if self.version_service:
729
+ return self.version_service.get_version()
1233
730
  else:
1234
- return f"v{version}"
1235
-
1236
- def _register_memory_hooks(self):
1237
- """Register memory integration hooks with the hook service.
1238
-
1239
- WHY: This activates the memory system by registering hooks that automatically
1240
- inject agent memory before delegation and extract learnings after delegation.
1241
- This is the critical connection point between the memory system and the CLI.
1242
-
1243
- DESIGN DECISION: We register hooks here instead of in __init__ to ensure
1244
- all services are initialized first. Hooks are only registered if the memory
1245
- system is enabled in configuration.
1246
- """
1247
- try:
1248
- # Only register if memory system is enabled
1249
- if not self.config.get('memory.enabled', True):
1250
- self.logger.debug("Memory system disabled - skipping hook registration")
1251
- return
1252
-
1253
- # Import hook classes (lazy import to avoid circular dependencies)
1254
- try:
1255
- from claude_mpm.hooks.memory_integration_hook import (
1256
- MemoryPreDelegationHook,
1257
- MemoryPostDelegationHook
1258
- )
1259
- except ImportError as e:
1260
- self.logger.warning(f"Memory integration hooks not available: {e}")
1261
- return
1262
-
1263
- # Register pre-delegation hook for memory injection
1264
- pre_hook = MemoryPreDelegationHook(self.config)
1265
- success = self.hook_service.register_hook(pre_hook)
1266
- if success:
1267
- self.logger.info(f"✅ Registered memory pre-delegation hook (priority: {pre_hook.priority})")
1268
- else:
1269
- self.logger.warning("❌ Failed to register memory pre-delegation hook")
1270
-
1271
- # Register post-delegation hook if auto-learning is enabled
1272
- if self.config.get('memory.auto_learning', True): # Default to True now
1273
- post_hook = MemoryPostDelegationHook(self.config)
1274
- success = self.hook_service.register_hook(post_hook)
1275
- if success:
1276
- self.logger.info(f"✅ Registered memory post-delegation hook (priority: {post_hook.priority})")
1277
- else:
1278
- self.logger.warning("❌ Failed to register memory post-delegation hook")
1279
- else:
1280
- self.logger.info("ℹ️ Auto-learning disabled - skipping post-delegation hook")
1281
-
1282
- # Log summary of registered hooks
1283
- hooks = self.hook_service.list_hooks()
1284
- pre_count = len(hooks.get('pre_delegation', []))
1285
- post_count = len(hooks.get('post_delegation', []))
1286
- self.logger.info(f"📋 Hook Service initialized: {pre_count} pre-delegation, {post_count} post-delegation hooks")
1287
-
1288
- except AttributeError as e:
1289
- self.logger.warning(f"Hook service not initialized properly: {e}")
1290
- except Exception as e:
1291
- self.logger.error(f"❌ Failed to register memory hooks: {e}")
1292
- # Don't fail the entire initialization - memory system is optional
1293
-
731
+ # Fallback if service not available
732
+ return "v0.0.0"
733
+
1294
734
  def _launch_subprocess_interactive(self, cmd: list, env: dict):
1295
735
  """Launch Claude as a subprocess with PTY for interactive mode.
1296
-
1297
- WHY: This method launches Claude as a subprocess when explicitly requested
1298
- (via --launch-method subprocess). Subprocess mode maintains the parent process,
1299
- which can be useful for:
1300
- 1. Maintaining WebSocket connections and monitoring
1301
- 2. Providing proper cleanup and error handling
1302
- 3. Debugging and development scenarios
1303
-
1304
- DESIGN DECISION: We use PTY (pseudo-terminal) to maintain full interactive
1305
- capabilities. Response logging is handled through the hook system, not I/O
1306
- interception, for better performance and compatibility.
736
+
737
+ Delegates to the SubprocessLauncherService for subprocess management.
1307
738
  """
1308
- import pty
1309
- import select
1310
- import termios
1311
- import tty
1312
- import signal
1313
-
1314
- # Note: Response logging is handled through the hook system,
1315
- # not through I/O interception (better performance)
1316
-
1317
- # Save original terminal settings
1318
- original_tty = None
1319
- if sys.stdin.isatty():
1320
- original_tty = termios.tcgetattr(sys.stdin)
1321
-
1322
- # Create PTY
1323
- master_fd, slave_fd = pty.openpty()
1324
-
1325
- try:
1326
- # Start Claude process
1327
- process = subprocess.Popen(
1328
- cmd,
1329
- stdin=slave_fd,
1330
- stdout=slave_fd,
1331
- stderr=slave_fd,
1332
- env=env
739
+ if self.subprocess_launcher_service:
740
+ self.subprocess_launcher_service.launch_subprocess_interactive(cmd, env)
741
+ else:
742
+ # Fallback if service is not available
743
+ self.logger.warning(
744
+ "Subprocess launcher service not available, cannot launch subprocess"
1333
745
  )
1334
-
1335
- # Close slave in parent
1336
- os.close(slave_fd)
1337
-
1338
- if self.project_logger:
1339
- self.project_logger.log_system(
1340
- f"Claude subprocess started with PID {process.pid}",
1341
- level="INFO",
1342
- component="subprocess"
1343
- )
1344
-
1345
- # Notify WebSocket clients
1346
- if self.websocket_server:
1347
- self.websocket_server.claude_status_changed(
1348
- status="running",
1349
- pid=process.pid,
1350
- message="Claude subprocess started"
1351
- )
1352
-
1353
- # Set terminal to raw mode for proper interaction
1354
- if sys.stdin.isatty():
1355
- tty.setraw(sys.stdin)
1356
-
1357
- # Handle Ctrl+C gracefully
1358
- def signal_handler(signum, frame):
1359
- if process.poll() is None:
1360
- process.terminate()
1361
- raise KeyboardInterrupt()
1362
-
1363
- signal.signal(signal.SIGINT, signal_handler)
1364
-
1365
- # I/O loop
1366
- while True:
1367
- # Check if process is still running
1368
- if process.poll() is not None:
1369
- break
1370
-
1371
- # Check for data from Claude or stdin
1372
- r, _, _ = select.select([master_fd, sys.stdin], [], [], 0)
1373
-
1374
- if master_fd in r:
1375
- try:
1376
- data = os.read(master_fd, 4096)
1377
- if data:
1378
- os.write(sys.stdout.fileno(), data)
1379
- # Broadcast output to WebSocket clients
1380
- if self.websocket_server:
1381
- try:
1382
- # Decode and send
1383
- output = data.decode('utf-8', errors='replace')
1384
- self.websocket_server.claude_output(output, "stdout")
1385
- except Exception as e:
1386
- self.logger.debug(f"Failed to broadcast output: {e}")
1387
- else:
1388
- break # EOF
1389
- except OSError:
1390
- break
1391
-
1392
- if sys.stdin in r:
1393
- try:
1394
- data = os.read(sys.stdin.fileno(), 4096)
1395
- if data:
1396
- os.write(master_fd, data)
1397
- except OSError:
1398
- break
1399
-
1400
- # Wait for process to complete
1401
- process.wait()
1402
-
1403
- # Note: Response logging is handled through the hook system
1404
-
1405
- if self.project_logger:
1406
- self.project_logger.log_system(
1407
- f"Claude subprocess exited with code {process.returncode}",
1408
- level="INFO",
1409
- component="subprocess"
1410
- )
1411
-
1412
- # Notify WebSocket clients
1413
- if self.websocket_server:
1414
- self.websocket_server.claude_status_changed(
1415
- status="stopped",
1416
- message=f"Claude subprocess exited with code {process.returncode}"
1417
- )
1418
-
1419
- finally:
1420
- # Restore terminal
1421
- if original_tty and sys.stdin.isatty():
1422
- termios.tcsetattr(sys.stdin, termios.TCSADRAIN, original_tty)
1423
-
1424
- # Close PTY
1425
- try:
1426
- os.close(master_fd)
1427
- except:
1428
- pass
1429
-
1430
- # Ensure process is terminated
1431
- if 'process' in locals() and process.poll() is None:
1432
- process.terminate()
1433
- try:
1434
- process.wait(timeout=2)
1435
- except subprocess.TimeoutExpired:
1436
- process.kill()
1437
- process.wait()
1438
-
1439
- # End WebSocket session if in subprocess mode
1440
- if self.websocket_server:
1441
- self.websocket_server.session_ended()
746
+ raise RuntimeError("Subprocess launcher service not available")
1442
747
 
1443
748
 
1444
749
  def create_simple_context() -> str:
@@ -1447,7 +752,7 @@ def create_simple_context() -> str:
1447
752
 
1448
753
  You have access to native subagents via the Task tool with subagent_type parameter:
1449
754
  - engineer: For coding, implementation, and technical tasks
1450
- - qa: For testing, validation, and quality assurance
755
+ - qa: For testing, validation, and quality assurance
1451
756
  - documentation: For docs, guides, and explanations
1452
757
  - research: For investigation and analysis
1453
758
  - security: For security-related tasks
@@ -1461,7 +766,7 @@ IMPORTANT: The Task tool accepts both naming formats:
1461
766
  - Capitalized format: "Research", "Engineer", "QA", "Version Control", "Data Engineer"
1462
767
  - Lowercase format: "research", "engineer", "qa", "version-control", "data-engineer"
1463
768
 
1464
- Both formats work correctly. When you see capitalized names (matching TodoWrite prefixes),
769
+ Both formats work correctly. When you see capitalized names (matching TodoWrite prefixes),
1465
770
  automatically normalize them to lowercase-hyphenated format for the Task tool.
1466
771
 
1467
772
  Work efficiently and delegate appropriately to subagents when needed."""
@@ -1485,4 +790,4 @@ def run_claude_oneshot(prompt: str, context: Optional[str] = None) -> bool:
1485
790
  runner = ClaudeRunner()
1486
791
  if context is None:
1487
792
  context = create_simple_context()
1488
- return runner.run_oneshot(prompt, context)
793
+ return runner.run_oneshot(prompt, context)