claude-mpm 3.9.9__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 (411) 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 +155 -0
  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 +90 -49
  31. claude_mpm/cli/__main__.py +3 -2
  32. claude_mpm/cli/commands/__init__.py +21 -18
  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 +143 -762
  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 -1150
  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 +217 -0
  69. claude_mpm/config/paths.py +94 -208
  70. claude_mpm/config/socketio_config.py +84 -73
  71. claude_mpm/constants.py +36 -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 +571 -0
  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 +40 -23
  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 +14 -21
  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 +97 -93
  298. claude_mpm/services/mcp_gateway/main.py +307 -127
  299. claude_mpm/services/mcp_gateway/registry/__init__.py +1 -1
  300. claude_mpm/services/mcp_gateway/registry/service_registry.py +100 -101
  301. claude_mpm/services/mcp_gateway/registry/tool_registry.py +135 -126
  302. claude_mpm/services/mcp_gateway/server/__init__.py +4 -4
  303. claude_mpm/services/mcp_gateway/server/{mcp_server.py → mcp_gateway.py} +149 -153
  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 +110 -121
  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 +20 -534
  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 +9 -0
  358. claude_mpm/storage/state_storage.py +552 -0
  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.9.dist-info → claude_mpm-4.0.3.dist-info}/METADATA +51 -2
  381. claude_mpm-4.0.3.dist-info/RECORD +402 -0
  382. {claude_mpm-3.9.9.dist-info → claude_mpm-4.0.3.dist-info}/entry_points.txt +1 -0
  383. {claude_mpm-3.9.9.dist-info → claude_mpm-4.0.3.dist-info}/licenses/LICENSE +1 -1
  384. claude_mpm/config/memory_guardian_config.py +0 -325
  385. claude_mpm/core/config_paths.py +0 -150
  386. claude_mpm/dashboard/static/js/dashboard-original.js +0 -4134
  387. claude_mpm/deployment_paths.py +0 -261
  388. claude_mpm/hooks/claude_hooks/hook_handler_fixed.py +0 -454
  389. claude_mpm/models/state_models.py +0 -433
  390. claude_mpm/services/agent/__init__.py +0 -24
  391. claude_mpm/services/agent/deployment.py +0 -2548
  392. claude_mpm/services/agent/management.py +0 -598
  393. claude_mpm/services/agent/registry.py +0 -813
  394. claude_mpm/services/agents/registry/agent_registry.py +0 -813
  395. claude_mpm/services/communication/socketio.py +0 -1935
  396. claude_mpm/services/communication/websocket.py +0 -479
  397. claude_mpm/services/framework_claude_md_generator.py +0 -624
  398. claude_mpm/services/health_monitor.py +0 -893
  399. claude_mpm/services/infrastructure/memory_guardian.py +0 -770
  400. claude_mpm/services/mcp_gateway/server/mcp_server_simple.py +0 -444
  401. claude_mpm/services/optimized_hook_service.py +0 -542
  402. claude_mpm/services/project_analyzer.py +0 -864
  403. claude_mpm/services/project_registry.py +0 -608
  404. claude_mpm/services/standalone_socketio_server.py +0 -1300
  405. claude_mpm/services/ticket_manager_di.py +0 -318
  406. claude_mpm/services/ticketing_service_original.py +0 -510
  407. claude_mpm/utils/paths.py +0 -395
  408. claude_mpm/utils/platform_memory.py +0 -524
  409. claude_mpm-3.9.9.dist-info/RECORD +0 -293
  410. {claude_mpm-3.9.9.dist-info → claude_mpm-4.0.3.dist-info}/WHEEL +0 -0
  411. {claude_mpm-3.9.9.dist-info → claude_mpm-4.0.3.dist-info}/top_level.txt +0 -0
@@ -17,44 +17,46 @@ to provide complete project lifecycle tracking.
17
17
  """
18
18
 
19
19
  import os
20
- import sys
21
- import uuid
22
- import subprocess
23
20
  import platform
24
21
  import shutil
25
- from datetime import datetime, timezone, timedelta
22
+ import subprocess
23
+ import sys
24
+ import uuid
25
+ from datetime import datetime, timedelta, timezone
26
26
  from pathlib import Path
27
- from typing import Dict, Any, Optional, List
27
+ from typing import Any, Dict, List, Optional
28
+
28
29
  import yaml
29
30
 
30
31
  from claude_mpm.core.logger import get_logger
31
- from claude_mpm.deployment_paths import get_project_root
32
+ from claude_mpm.core.unified_paths import get_project_root
32
33
 
33
34
 
34
35
  class ProjectRegistryError(Exception):
35
36
  """Base exception for project registry operations."""
37
+
36
38
  pass
37
39
 
38
40
 
39
41
  class ProjectRegistry:
40
42
  """
41
43
  Manages the project registry for claude-mpm installations.
42
-
44
+
43
45
  WHY: The project registry provides persistent project tracking across sessions,
44
46
  enabling project identification, metadata collection, and usage analytics.
45
47
  This is crucial for multi-project environments where users switch between
46
48
  different codebases.
47
-
49
+
48
50
  DESIGN DECISION: Registry files are stored in ~/.claude-mpm/registry/
49
51
  with UUID-based filenames to ensure uniqueness and avoid conflicts. The registry
50
52
  uses YAML for human readability and ease of manual inspection/editing.
51
53
  Registry is stored in the user's home directory for persistence across projects.
52
54
  """
53
-
55
+
54
56
  def __init__(self):
55
57
  """
56
58
  Initialize the project registry.
57
-
59
+
58
60
  WHY: Sets up the registry directory and logger. The registry directory
59
61
  is created in the user's home directory to keep registry data user-specific
60
62
  and persistent across different projects and sessions.
@@ -65,189 +67,189 @@ class ProjectRegistry:
65
67
  user_home = Path.home()
66
68
  self.registry_dir = user_home / ".claude-mpm" / "registry"
67
69
  self.current_project_path = Path.cwd().resolve()
68
-
70
+
69
71
  # Ensure registry directory exists
70
72
  try:
71
73
  self.registry_dir.mkdir(parents=True, exist_ok=True)
72
74
  except Exception as e:
73
75
  self.logger.error(f"Failed to create registry directory: {e}")
74
76
  raise ProjectRegistryError(f"Cannot create registry directory: {e}")
75
-
77
+
76
78
  def get_or_create_project_entry(self) -> Dict[str, Any]:
77
79
  """
78
80
  Get existing project registry entry or create a new one.
79
-
81
+
80
82
  WHY: This is the main entry point for project registration. It handles
81
83
  both new project registration and existing project updates, ensuring
82
84
  that every claude-mpm session is properly tracked.
83
-
85
+
84
86
  DESIGN DECISION: Matching is done by normalized absolute path to handle
85
87
  symbolic links and different path representations consistently.
86
-
88
+
87
89
  Returns:
88
90
  Dictionary containing the project registry data
89
-
91
+
90
92
  Raises:
91
93
  ProjectRegistryError: If registry operations fail
92
94
  """
93
95
  try:
94
96
  # Look for existing registry entry
95
97
  existing_entry = self._find_existing_entry()
96
-
98
+
97
99
  if existing_entry:
98
- self.logger.debug(f"Found existing project entry: {existing_entry['project_id']}")
100
+ self.logger.debug(
101
+ f"Found existing project entry: {existing_entry['project_id']}"
102
+ )
99
103
  # Update existing entry with current session info
100
104
  return self._update_existing_entry(existing_entry)
101
105
  else:
102
106
  self.logger.debug("Creating new project registry entry")
103
107
  # Create new entry
104
108
  return self._create_new_entry()
105
-
109
+
106
110
  except Exception as e:
107
111
  self.logger.error(f"Failed to get or create project entry: {e}")
108
112
  raise ProjectRegistryError(f"Registry operation failed: {e}")
109
-
113
+
110
114
  def _find_existing_entry(self) -> Optional[Dict[str, Any]]:
111
115
  """
112
116
  Search for existing registry entry matching current project path.
113
-
117
+
114
118
  WHY: We need to match projects by their absolute path to avoid creating
115
119
  duplicate entries when the same project is accessed from different
116
120
  working directories or via different path representations.
117
-
121
+
118
122
  Returns:
119
123
  Existing registry data if found, None otherwise
120
124
  """
121
125
  try:
122
126
  # Normalize current path for consistent matching
123
127
  current_path_str = str(self.current_project_path)
124
-
128
+
125
129
  # Search all registry files
126
130
  for registry_file in self.registry_dir.glob("*.yaml"):
127
131
  try:
128
- with open(registry_file, 'r', encoding='utf-8') as f:
132
+ with open(registry_file, "r", encoding="utf-8") as f:
129
133
  data = yaml.safe_load(f) or {}
130
-
134
+
131
135
  # Check if project_path matches
132
- if data.get('project_path') == current_path_str:
133
- data['_registry_file'] = registry_file # Add file reference
136
+ if data.get("project_path") == current_path_str:
137
+ data["_registry_file"] = registry_file # Add file reference
134
138
  return data
135
-
139
+
136
140
  except Exception as e:
137
- self.logger.warning(f"Failed to read registry file {registry_file}: {e}")
141
+ self.logger.warning(
142
+ f"Failed to read registry file {registry_file}: {e}"
143
+ )
138
144
  continue
139
-
145
+
140
146
  return None
141
-
147
+
142
148
  except Exception as e:
143
149
  self.logger.error(f"Error searching for existing entry: {e}")
144
150
  return None
145
-
151
+
146
152
  def _create_new_entry(self) -> Dict[str, Any]:
147
153
  """
148
154
  Create a new project registry entry.
149
-
155
+
150
156
  WHY: New projects need to be registered with comprehensive metadata
151
157
  including project information, environment details, and initial runtime
152
158
  data. This creates a complete snapshot of the project at first access.
153
-
159
+
154
160
  Returns:
155
161
  Newly created registry data
156
162
  """
157
163
  project_id = str(uuid.uuid4())
158
164
  registry_file = self.registry_dir / f"{project_id}.yaml"
159
-
165
+
160
166
  # Build comprehensive project data
161
167
  project_data = {
162
- 'project_id': project_id,
163
- 'project_path': str(self.current_project_path),
164
- 'project_name': self.current_project_path.name,
165
- 'metadata': self._build_metadata(is_new=True),
166
- 'runtime': self._build_runtime_info(),
167
- 'environment': self._build_environment_info(),
168
- 'git': self._build_git_info(),
169
- 'session': self._build_session_info(),
170
- 'project_info': self._build_project_info()
168
+ "project_id": project_id,
169
+ "project_path": str(self.current_project_path),
170
+ "project_name": self.current_project_path.name,
171
+ "metadata": self._build_metadata(is_new=True),
172
+ "runtime": self._build_runtime_info(),
173
+ "environment": self._build_environment_info(),
174
+ "git": self._build_git_info(),
175
+ "session": self._build_session_info(),
176
+ "project_info": self._build_project_info(),
171
177
  }
172
-
178
+
173
179
  # Save to registry file
174
180
  self._save_registry_data(registry_file, project_data)
175
- project_data['_registry_file'] = registry_file
176
-
181
+ project_data["_registry_file"] = registry_file
182
+
177
183
  self.logger.info(f"Created new project registry entry: {project_id}")
178
184
  return project_data
179
-
185
+
180
186
  def _update_existing_entry(self, existing_data: Dict[str, Any]) -> Dict[str, Any]:
181
187
  """
182
188
  Update existing project registry entry with current session information.
183
-
189
+
184
190
  WHY: Existing projects need their metadata updated to reflect current
185
191
  access patterns, runtime information, and any changes in project state.
186
192
  This maintains accurate usage tracking and project state history.
187
-
193
+
188
194
  Args:
189
195
  existing_data: The existing registry data to update
190
-
196
+
191
197
  Returns:
192
198
  Updated registry data
193
199
  """
194
- registry_file = existing_data.get('_registry_file')
200
+ registry_file = existing_data.get("_registry_file")
195
201
  if not registry_file:
196
- raise ProjectRegistryError("Registry file reference missing from existing data")
197
-
202
+ raise ProjectRegistryError(
203
+ "Registry file reference missing from existing data"
204
+ )
205
+
198
206
  # Update timestamps and counters
199
- metadata = existing_data.get('metadata', {})
200
- access_count = metadata.get('access_count', 0) + 1
207
+ metadata = existing_data.get("metadata", {})
208
+ access_count = metadata.get("access_count", 0) + 1
201
209
  now = datetime.now(timezone.utc).isoformat()
202
-
203
- existing_data['metadata'].update({
204
- 'updated_at': now,
205
- 'last_accessed': now,
206
- 'access_count': access_count
207
- })
208
-
210
+
211
+ existing_data["metadata"].update(
212
+ {"updated_at": now, "last_accessed": now, "access_count": access_count}
213
+ )
214
+
209
215
  # Update runtime information
210
- existing_data['runtime'] = self._build_runtime_info()
211
-
216
+ existing_data["runtime"] = self._build_runtime_info()
217
+
212
218
  # Update session information
213
- existing_data['session'] = self._build_session_info()
214
-
219
+ existing_data["session"] = self._build_session_info()
220
+
215
221
  # Update git information (may have changed)
216
- existing_data['git'] = self._build_git_info()
217
-
222
+ existing_data["git"] = self._build_git_info()
223
+
218
224
  # Update project info (may have changed)
219
- existing_data['project_info'] = self._build_project_info()
220
-
225
+ existing_data["project_info"] = self._build_project_info()
226
+
221
227
  # Save updated data
222
228
  self._save_registry_data(registry_file, existing_data)
223
-
229
+
224
230
  self.logger.debug(f"Updated project registry entry (access #{access_count})")
225
231
  return existing_data
226
-
232
+
227
233
  def _build_metadata(self, is_new: bool = False) -> Dict[str, Any]:
228
234
  """
229
235
  Build metadata section for registry entry.
230
-
236
+
231
237
  WHY: Metadata tracks creation, modification, and access patterns for
232
238
  analytics and project lifecycle management.
233
239
  """
234
240
  now = datetime.now(timezone.utc).isoformat()
235
-
236
- metadata = {
237
- 'updated_at': now,
238
- 'last_accessed': now,
239
- 'access_count': 1
240
- }
241
-
241
+
242
+ metadata = {"updated_at": now, "last_accessed": now, "access_count": 1}
243
+
242
244
  if is_new:
243
- metadata['created_at'] = now
244
-
245
+ metadata["created_at"] = now
246
+
245
247
  return metadata
246
-
248
+
247
249
  def _build_runtime_info(self) -> Dict[str, Any]:
248
250
  """
249
251
  Build runtime information section.
250
-
252
+
251
253
  WHY: Runtime information helps track session lifecycle, process management,
252
254
  and system state. This is valuable for debugging session issues and
253
255
  understanding usage patterns.
@@ -257,128 +259,128 @@ class ProjectRegistry:
257
259
  from claude_mpm import __version__ as claude_mpm_version
258
260
  except ImportError:
259
261
  claude_mpm_version = "unknown"
260
-
262
+
261
263
  return {
262
- 'startup_time': datetime.now(timezone.utc).isoformat(),
263
- 'pid': os.getpid(),
264
- 'python_version': f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}",
265
- 'claude_mpm_version': claude_mpm_version,
266
- 'command_line': ' '.join(sys.argv),
267
- 'launch_method': 'subprocess' # Default, could be detected based on parent process
264
+ "startup_time": datetime.now(timezone.utc).isoformat(),
265
+ "pid": os.getpid(),
266
+ "python_version": f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}",
267
+ "claude_mpm_version": claude_mpm_version,
268
+ "command_line": " ".join(sys.argv),
269
+ "launch_method": "subprocess", # Default, could be detected based on parent process
268
270
  }
269
-
271
+
270
272
  def _build_environment_info(self) -> Dict[str, Any]:
271
273
  """
272
274
  Build environment information section.
273
-
275
+
274
276
  WHY: Environment information helps with debugging, platform-specific
275
277
  behavior analysis, and provides context for project usage patterns
276
278
  across different systems and user setups.
277
279
  """
278
280
  return {
279
- 'user': os.getenv('USER') or os.getenv('USERNAME', 'unknown'),
280
- 'hostname': platform.node(),
281
- 'os': platform.system(),
282
- 'os_version': platform.release(),
283
- 'shell': os.getenv('SHELL', 'unknown'),
284
- 'terminal': os.getenv('TERM', 'unknown'),
285
- 'cwd': str(Path.cwd())
281
+ "user": os.getenv("USER") or os.getenv("USERNAME", "unknown"),
282
+ "hostname": platform.node(),
283
+ "os": platform.system(),
284
+ "os_version": platform.release(),
285
+ "shell": os.getenv("SHELL", "unknown"),
286
+ "terminal": os.getenv("TERM", "unknown"),
287
+ "cwd": str(Path.cwd()),
286
288
  }
287
-
289
+
288
290
  def _build_git_info(self) -> Dict[str, Any]:
289
291
  """
290
292
  Build git repository information.
291
-
293
+
292
294
  WHY: Git information is crucial for project identification and tracking
293
295
  changes across different branches and commits. This helps understand
294
296
  project state and enables better project management features.
295
297
  """
296
298
  git_info = {
297
- 'is_repo': False,
298
- 'branch': None,
299
- 'remote_url': None,
300
- 'last_commit': None,
301
- 'has_uncommitted': False
299
+ "is_repo": False,
300
+ "branch": None,
301
+ "remote_url": None,
302
+ "last_commit": None,
303
+ "has_uncommitted": False,
302
304
  }
303
-
305
+
304
306
  try:
305
307
  # Check if we're in a git repository
306
308
  result = subprocess.run(
307
- ['git', 'rev-parse', '--git-dir'],
309
+ ["git", "rev-parse", "--git-dir"],
308
310
  cwd=self.current_project_path,
309
311
  capture_output=True,
310
312
  text=True,
311
- timeout=5
313
+ timeout=5,
312
314
  )
313
-
315
+
314
316
  if result.returncode == 0:
315
- git_info['is_repo'] = True
316
-
317
+ git_info["is_repo"] = True
318
+
317
319
  # Get current branch
318
320
  try:
319
321
  result = subprocess.run(
320
- ['git', 'branch', '--show-current'],
322
+ ["git", "branch", "--show-current"],
321
323
  cwd=self.current_project_path,
322
324
  capture_output=True,
323
325
  text=True,
324
- timeout=5
326
+ timeout=5,
325
327
  )
326
328
  if result.returncode == 0:
327
- git_info['branch'] = result.stdout.strip()
329
+ git_info["branch"] = result.stdout.strip()
328
330
  except Exception:
329
331
  pass
330
-
332
+
331
333
  # Get remote URL
332
334
  try:
333
335
  result = subprocess.run(
334
- ['git', 'remote', 'get-url', 'origin'],
336
+ ["git", "remote", "get-url", "origin"],
335
337
  cwd=self.current_project_path,
336
338
  capture_output=True,
337
339
  text=True,
338
- timeout=5
340
+ timeout=5,
339
341
  )
340
342
  if result.returncode == 0:
341
- git_info['remote_url'] = result.stdout.strip()
343
+ git_info["remote_url"] = result.stdout.strip()
342
344
  except Exception:
343
345
  pass
344
-
346
+
345
347
  # Get last commit
346
348
  try:
347
349
  result = subprocess.run(
348
- ['git', 'rev-parse', 'HEAD'],
350
+ ["git", "rev-parse", "HEAD"],
349
351
  cwd=self.current_project_path,
350
352
  capture_output=True,
351
353
  text=True,
352
- timeout=5
354
+ timeout=5,
353
355
  )
354
356
  if result.returncode == 0:
355
- git_info['last_commit'] = result.stdout.strip()
357
+ git_info["last_commit"] = result.stdout.strip()
356
358
  except Exception:
357
359
  pass
358
-
360
+
359
361
  # Check for uncommitted changes
360
362
  try:
361
363
  result = subprocess.run(
362
- ['git', 'status', '--porcelain'],
364
+ ["git", "status", "--porcelain"],
363
365
  cwd=self.current_project_path,
364
366
  capture_output=True,
365
367
  text=True,
366
- timeout=5
368
+ timeout=5,
367
369
  )
368
370
  if result.returncode == 0:
369
- git_info['has_uncommitted'] = bool(result.stdout.strip())
371
+ git_info["has_uncommitted"] = bool(result.stdout.strip())
370
372
  except Exception:
371
373
  pass
372
-
374
+
373
375
  except Exception as e:
374
376
  self.logger.debug(f"Failed to get git info: {e}")
375
-
377
+
376
378
  return git_info
377
-
379
+
378
380
  def _build_session_info(self) -> Dict[str, Any]:
379
381
  """
380
382
  Build session information.
381
-
383
+
382
384
  WHY: Session information tracks the current claude-mpm session state,
383
385
  including active components and configuration. This helps with session
384
386
  management and debugging.
@@ -386,223 +388,243 @@ class ProjectRegistry:
386
388
  # These would be populated by the actual session manager
387
389
  # For now, we provide placeholders that can be updated by the caller
388
390
  return {
389
- 'session_id': None, # Could be set by session manager
390
- 'ticket_count': 0, # Could be updated by ticket manager
391
- 'agent_count': 0, # Could be updated by agent manager
392
- 'hooks_enabled': False, # Could be detected from configuration
393
- 'monitor_enabled': False # Could be detected from process state
391
+ "session_id": None, # Could be set by session manager
392
+ "ticket_count": 0, # Could be updated by ticket manager
393
+ "agent_count": 0, # Could be updated by agent manager
394
+ "hooks_enabled": False, # Could be detected from configuration
395
+ "monitor_enabled": False, # Could be detected from process state
394
396
  }
395
-
397
+
396
398
  def _build_project_info(self) -> Dict[str, Any]:
397
399
  """
398
400
  Build project information section.
399
-
401
+
400
402
  WHY: Project information helps identify the type of project and its
401
403
  characteristics, enabling better tool selection and project-specific
402
404
  optimizations.
403
405
  """
404
406
  project_info = {
405
- 'has_claude_config': False,
406
- 'has_claude_md': False,
407
- 'has_pyproject': False,
408
- 'has_package_json': False,
409
- 'project_type': 'unknown'
407
+ "has_claude_config": False,
408
+ "has_claude_md": False,
409
+ "has_pyproject": False,
410
+ "has_package_json": False,
411
+ "project_type": "unknown",
410
412
  }
411
-
413
+
412
414
  # Check for various project files
413
- claude_files = ['.claude', 'claude.toml', '.claude.toml']
415
+ claude_files = [".claude", "claude.toml", ".claude.toml"]
414
416
  for claude_file in claude_files:
415
417
  if (self.current_project_path / claude_file).exists():
416
- project_info['has_claude_config'] = True
418
+ project_info["has_claude_config"] = True
417
419
  break
418
-
419
- claude_md_files = ['CLAUDE.md', 'claude.md', '.claude.md']
420
+
421
+ claude_md_files = ["CLAUDE.md", "claude.md", ".claude.md"]
420
422
  for claude_md in claude_md_files:
421
423
  if (self.current_project_path / claude_md).exists():
422
- project_info['has_claude_md'] = True
424
+ project_info["has_claude_md"] = True
423
425
  break
424
-
425
- if (self.current_project_path / 'pyproject.toml').exists():
426
- project_info['has_pyproject'] = True
427
-
428
- if (self.current_project_path / 'package.json').exists():
429
- project_info['has_package_json'] = True
430
-
426
+
427
+ if (self.current_project_path / "pyproject.toml").exists():
428
+ project_info["has_pyproject"] = True
429
+
430
+ if (self.current_project_path / "package.json").exists():
431
+ project_info["has_package_json"] = True
432
+
431
433
  # Determine project type
432
- if project_info['has_pyproject'] or (self.current_project_path / 'setup.py').exists():
433
- project_info['project_type'] = 'python'
434
- elif project_info['has_package_json']:
435
- project_info['project_type'] = 'javascript'
436
- elif (self.current_project_path / 'Cargo.toml').exists():
437
- project_info['project_type'] = 'rust'
438
- elif (self.current_project_path / 'go.mod').exists():
439
- project_info['project_type'] = 'go'
440
- elif (self.current_project_path / 'pom.xml').exists():
441
- project_info['project_type'] = 'java'
442
- elif any((self.current_project_path / ext).exists() for ext in ['*.c', '*.cpp', '*.h', '*.hpp']):
443
- project_info['project_type'] = 'c/cpp'
444
- elif project_info['has_claude_config'] or project_info['has_claude_md']:
445
- project_info['project_type'] = 'claude'
434
+ if (
435
+ project_info["has_pyproject"]
436
+ or (self.current_project_path / "setup.py").exists()
437
+ ):
438
+ project_info["project_type"] = "python"
439
+ elif project_info["has_package_json"]:
440
+ project_info["project_type"] = "javascript"
441
+ elif (self.current_project_path / "Cargo.toml").exists():
442
+ project_info["project_type"] = "rust"
443
+ elif (self.current_project_path / "go.mod").exists():
444
+ project_info["project_type"] = "go"
445
+ elif (self.current_project_path / "pom.xml").exists():
446
+ project_info["project_type"] = "java"
447
+ elif any(
448
+ (self.current_project_path / ext).exists()
449
+ for ext in ["*.c", "*.cpp", "*.h", "*.hpp"]
450
+ ):
451
+ project_info["project_type"] = "c/cpp"
452
+ elif project_info["has_claude_config"] or project_info["has_claude_md"]:
453
+ project_info["project_type"] = "claude"
446
454
  else:
447
455
  # Try to detect by file extensions
448
- common_files = list(self.current_project_path.iterdir())[:20] # Check first 20 files
456
+ common_files = list(self.current_project_path.iterdir())[
457
+ :20
458
+ ] # Check first 20 files
449
459
  extensions = {f.suffix.lower() for f in common_files if f.is_file()}
450
-
451
- if '.py' in extensions:
452
- project_info['project_type'] = 'python'
453
- elif any(ext in extensions for ext in ['.js', '.ts', '.jsx', '.tsx']):
454
- project_info['project_type'] = 'javascript'
455
- elif any(ext in extensions for ext in ['.rs']):
456
- project_info['project_type'] = 'rust'
457
- elif any(ext in extensions for ext in ['.go']):
458
- project_info['project_type'] = 'go'
459
- elif any(ext in extensions for ext in ['.java', '.scala', '.kt']):
460
- project_info['project_type'] = 'jvm'
461
- elif any(ext in extensions for ext in ['.c', '.cpp', '.cc', '.h', '.hpp']):
462
- project_info['project_type'] = 'c/cpp'
463
- elif any(ext in extensions for ext in ['.md', '.txt', '.rst']):
464
- project_info['project_type'] = 'documentation'
465
-
460
+
461
+ if ".py" in extensions:
462
+ project_info["project_type"] = "python"
463
+ elif any(ext in extensions for ext in [".js", ".ts", ".jsx", ".tsx"]):
464
+ project_info["project_type"] = "javascript"
465
+ elif any(ext in extensions for ext in [".rs"]):
466
+ project_info["project_type"] = "rust"
467
+ elif any(ext in extensions for ext in [".go"]):
468
+ project_info["project_type"] = "go"
469
+ elif any(ext in extensions for ext in [".java", ".scala", ".kt"]):
470
+ project_info["project_type"] = "jvm"
471
+ elif any(ext in extensions for ext in [".c", ".cpp", ".cc", ".h", ".hpp"]):
472
+ project_info["project_type"] = "c/cpp"
473
+ elif any(ext in extensions for ext in [".md", ".txt", ".rst"]):
474
+ project_info["project_type"] = "documentation"
475
+
466
476
  return project_info
467
-
477
+
468
478
  def _save_registry_data(self, registry_file: Path, data: Dict[str, Any]) -> None:
469
479
  """
470
480
  Save registry data to YAML file.
471
-
481
+
472
482
  WHY: Centralized saving logic ensures consistent formatting and error
473
483
  handling across all registry operations. YAML format provides human
474
484
  readability for debugging and manual inspection.
475
-
485
+
476
486
  Args:
477
487
  registry_file: Path to the registry file
478
488
  data: Registry data to save
479
489
  """
480
490
  try:
481
491
  # Remove internal fields before saving
482
- save_data = {k: v for k, v in data.items() if not k.startswith('_')}
483
-
484
- with open(registry_file, 'w', encoding='utf-8') as f:
485
- yaml.dump(save_data, f, default_flow_style=False, sort_keys=False, indent=2)
486
-
492
+ save_data = {k: v for k, v in data.items() if not k.startswith("_")}
493
+
494
+ with open(registry_file, "w", encoding="utf-8") as f:
495
+ yaml.dump(
496
+ save_data, f, default_flow_style=False, sort_keys=False, indent=2
497
+ )
498
+
487
499
  self.logger.debug(f"Saved registry data to {registry_file}")
488
-
500
+
489
501
  except Exception as e:
490
502
  self.logger.error(f"Failed to save registry data: {e}")
491
503
  raise ProjectRegistryError(f"Failed to save registry: {e}")
492
-
504
+
493
505
  def list_projects(self) -> List[Dict[str, Any]]:
494
506
  """
495
507
  List all registered projects.
496
-
508
+
497
509
  WHY: Provides visibility into all projects managed by claude-mpm,
498
510
  useful for project management and analytics.
499
-
511
+
500
512
  Returns:
501
513
  List of project registry data dictionaries
502
514
  """
503
515
  projects = []
504
-
516
+
505
517
  try:
506
518
  for registry_file in self.registry_dir.glob("*.yaml"):
507
519
  try:
508
- with open(registry_file, 'r', encoding='utf-8') as f:
520
+ with open(registry_file, "r", encoding="utf-8") as f:
509
521
  data = yaml.safe_load(f) or {}
510
522
  projects.append(data)
511
523
  except Exception as e:
512
- self.logger.warning(f"Failed to read registry file {registry_file}: {e}")
524
+ self.logger.warning(
525
+ f"Failed to read registry file {registry_file}: {e}"
526
+ )
513
527
  continue
514
-
528
+
515
529
  except Exception as e:
516
530
  self.logger.error(f"Failed to list projects: {e}")
517
-
531
+
518
532
  return projects
519
-
533
+
520
534
  def cleanup_old_entries(self, max_age_days: int = 90) -> int:
521
535
  """
522
536
  Clean up old registry entries that haven't been accessed recently.
523
-
537
+
524
538
  WHY: Prevents registry directory from growing indefinitely with old
525
539
  project entries. Keeps the registry focused on active projects.
526
-
540
+
527
541
  Args:
528
542
  max_age_days: Maximum age in days for keeping entries
529
-
543
+
530
544
  Returns:
531
545
  Number of entries cleaned up
532
546
  """
533
547
  if max_age_days <= 0:
534
548
  return 0
535
-
549
+
536
550
  cleaned_count = 0
537
551
  cutoff_date = datetime.now(timezone.utc) - timedelta(days=max_age_days)
538
-
552
+
539
553
  try:
540
554
  for registry_file in self.registry_dir.glob("*.yaml"):
541
555
  try:
542
- with open(registry_file, 'r', encoding='utf-8') as f:
556
+ with open(registry_file, "r", encoding="utf-8") as f:
543
557
  data = yaml.safe_load(f) or {}
544
-
558
+
545
559
  # Check last accessed time
546
- last_accessed_str = data.get('metadata', {}).get('last_accessed')
560
+ last_accessed_str = data.get("metadata", {}).get("last_accessed")
547
561
  if last_accessed_str:
548
- last_accessed = datetime.fromisoformat(last_accessed_str.replace('Z', '+00:00'))
562
+ last_accessed = datetime.fromisoformat(
563
+ last_accessed_str.replace("Z", "+00:00")
564
+ )
549
565
  if last_accessed < cutoff_date:
550
566
  registry_file.unlink()
551
567
  cleaned_count += 1
552
- self.logger.debug(f"Cleaned up old registry entry: {registry_file}")
553
-
568
+ self.logger.debug(
569
+ f"Cleaned up old registry entry: {registry_file}"
570
+ )
571
+
554
572
  except Exception as e:
555
- self.logger.warning(f"Failed to process registry file {registry_file}: {e}")
573
+ self.logger.warning(
574
+ f"Failed to process registry file {registry_file}: {e}"
575
+ )
556
576
  continue
557
-
577
+
558
578
  except Exception as e:
559
579
  self.logger.error(f"Failed to cleanup old entries: {e}")
560
-
580
+
561
581
  if cleaned_count > 0:
562
582
  self.logger.info(f"Cleaned up {cleaned_count} old registry entries")
563
-
583
+
564
584
  return cleaned_count
565
-
585
+
566
586
  def update_session_info(self, session_updates: Dict[str, Any]) -> bool:
567
587
  """
568
588
  Update session information for the current project.
569
-
589
+
570
590
  WHY: Allows other components (session manager, ticket manager, etc.)
571
591
  to update the registry with current session state information.
572
-
592
+
573
593
  Args:
574
594
  session_updates: Dictionary of session updates to apply
575
-
595
+
576
596
  Returns:
577
597
  True if update was successful, False otherwise
578
598
  """
579
599
  try:
580
600
  existing_entry = self._find_existing_entry()
581
601
  if not existing_entry:
582
- self.logger.warning("No existing registry entry found for session update")
602
+ self.logger.warning(
603
+ "No existing registry entry found for session update"
604
+ )
583
605
  return False
584
-
585
- registry_file = existing_entry.get('_registry_file')
606
+
607
+ registry_file = existing_entry.get("_registry_file")
586
608
  if not registry_file:
587
609
  self.logger.error("Registry file reference missing")
588
610
  return False
589
-
611
+
590
612
  # Update session information
591
- if 'session' not in existing_entry:
592
- existing_entry['session'] = {}
593
-
594
- existing_entry['session'].update(session_updates)
595
-
613
+ if "session" not in existing_entry:
614
+ existing_entry["session"] = {}
615
+
616
+ existing_entry["session"].update(session_updates)
617
+
596
618
  # Update metadata timestamp
597
619
  now = datetime.now(timezone.utc).isoformat()
598
- existing_entry['metadata']['updated_at'] = now
599
-
620
+ existing_entry["metadata"]["updated_at"] = now
621
+
600
622
  # Save updated data
601
623
  self._save_registry_data(registry_file, existing_entry)
602
-
624
+
603
625
  self.logger.debug(f"Updated session info: {session_updates}")
604
626
  return True
605
-
627
+
606
628
  except Exception as e:
607
629
  self.logger.error(f"Failed to update session info: {e}")
608
- return False
630
+ return False