claude-mpm 3.4.10__py3-none-any.whl → 5.4.55__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.

Potentially problematic release.


This version of claude-mpm might be problematic. Click here for more details.

Files changed (950) hide show
  1. claude_mpm/BUILD_NUMBER +1 -0
  2. claude_mpm/VERSION +1 -0
  3. claude_mpm/__init__.py +50 -12
  4. claude_mpm/__main__.py +7 -2
  5. claude_mpm/agents/BASE_AGENT.md +164 -0
  6. claude_mpm/agents/BASE_ENGINEER.md +658 -0
  7. claude_mpm/agents/CLAUDE_MPM_OUTPUT_STYLE.md +290 -0
  8. claude_mpm/agents/CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md +2002 -0
  9. claude_mpm/agents/MEMORY.md +72 -0
  10. claude_mpm/agents/PM_INSTRUCTIONS.md +1402 -0
  11. claude_mpm/agents/WORKFLOW.md +111 -0
  12. claude_mpm/agents/__init__.py +92 -80
  13. claude_mpm/agents/agent-template.yaml +83 -0
  14. claude_mpm/agents/agent_loader.py +560 -745
  15. claude_mpm/agents/agent_loader_integration.py +53 -55
  16. claude_mpm/agents/agents_metadata.py +186 -27
  17. claude_mpm/agents/async_agent_loader.py +436 -0
  18. claude_mpm/agents/base_agent.json +8 -4
  19. claude_mpm/agents/frontmatter_validator.py +754 -0
  20. claude_mpm/agents/system_agent_config.py +222 -155
  21. claude_mpm/agents/templates/README.md +465 -0
  22. claude_mpm/agents/templates/__init__.py +17 -13
  23. claude_mpm/agents/templates/circuit-breakers.md +1391 -0
  24. claude_mpm/agents/templates/context-management-examples.md +544 -0
  25. claude_mpm/agents/templates/git-file-tracking.md +584 -0
  26. claude_mpm/agents/templates/pm-examples.md +474 -0
  27. claude_mpm/agents/templates/pm-red-flags.md +310 -0
  28. claude_mpm/agents/templates/pr-workflow-examples.md +427 -0
  29. claude_mpm/agents/templates/research-gate-examples.md +669 -0
  30. claude_mpm/agents/templates/response-format.md +583 -0
  31. claude_mpm/agents/templates/structured-questions-examples.md +615 -0
  32. claude_mpm/agents/templates/ticket-completeness-examples.md +139 -0
  33. claude_mpm/agents/templates/ticketing-examples.md +277 -0
  34. claude_mpm/agents/templates/validation-templates.md +312 -0
  35. claude_mpm/cli/__init__.py +90 -128
  36. claude_mpm/cli/__main__.py +33 -0
  37. claude_mpm/cli/chrome_devtools_installer.py +175 -0
  38. claude_mpm/cli/commands/__init__.py +36 -12
  39. claude_mpm/cli/commands/agent_manager.py +1403 -0
  40. claude_mpm/cli/commands/agent_source.py +774 -0
  41. claude_mpm/cli/commands/agent_state_manager.py +335 -0
  42. claude_mpm/cli/commands/agents.py +2503 -168
  43. claude_mpm/cli/commands/agents_cleanup.py +210 -0
  44. claude_mpm/cli/commands/agents_discover.py +338 -0
  45. claude_mpm/cli/commands/aggregate.py +540 -0
  46. claude_mpm/cli/commands/analyze.py +553 -0
  47. claude_mpm/cli/commands/analyze_code.py +528 -0
  48. claude_mpm/cli/commands/auto_configure.py +1053 -0
  49. claude_mpm/cli/commands/cleanup.py +588 -0
  50. claude_mpm/cli/commands/cleanup_orphaned_agents.py +150 -0
  51. claude_mpm/cli/commands/config.py +586 -0
  52. claude_mpm/cli/commands/configure.py +2654 -0
  53. claude_mpm/cli/commands/configure_agent_display.py +282 -0
  54. claude_mpm/cli/commands/configure_behavior_manager.py +204 -0
  55. claude_mpm/cli/commands/configure_hook_manager.py +225 -0
  56. claude_mpm/cli/commands/configure_models.py +18 -0
  57. claude_mpm/cli/commands/configure_navigation.py +184 -0
  58. claude_mpm/cli/commands/configure_paths.py +104 -0
  59. claude_mpm/cli/commands/configure_persistence.py +254 -0
  60. claude_mpm/cli/commands/configure_startup_manager.py +646 -0
  61. claude_mpm/cli/commands/configure_template_editor.py +497 -0
  62. claude_mpm/cli/commands/configure_validators.py +73 -0
  63. claude_mpm/cli/commands/dashboard.py +286 -0
  64. claude_mpm/cli/commands/debug.py +1386 -0
  65. claude_mpm/cli/commands/doctor.py +243 -0
  66. claude_mpm/cli/commands/hook_errors.py +277 -0
  67. claude_mpm/cli/commands/info.py +195 -74
  68. claude_mpm/cli/commands/local_deploy.py +534 -0
  69. claude_mpm/cli/commands/mcp.py +205 -0
  70. claude_mpm/cli/commands/mcp_command_router.py +161 -0
  71. claude_mpm/cli/commands/mcp_config.py +154 -0
  72. claude_mpm/cli/commands/mcp_config_commands.py +20 -0
  73. claude_mpm/cli/commands/mcp_external_commands.py +249 -0
  74. claude_mpm/cli/commands/mcp_install_commands.py +346 -0
  75. claude_mpm/cli/commands/mcp_pipx_config.py +208 -0
  76. claude_mpm/cli/commands/mcp_server_commands.py +155 -0
  77. claude_mpm/cli/commands/mcp_setup_external.py +868 -0
  78. claude_mpm/cli/commands/mcp_tool_commands.py +34 -0
  79. claude_mpm/cli/commands/memory.py +585 -846
  80. claude_mpm/cli/commands/monitor.py +228 -310
  81. claude_mpm/cli/commands/mpm_init/__init__.py +73 -0
  82. claude_mpm/cli/commands/mpm_init/core.py +759 -0
  83. claude_mpm/cli/commands/mpm_init/display.py +341 -0
  84. claude_mpm/cli/commands/mpm_init/git_activity.py +427 -0
  85. claude_mpm/cli/commands/mpm_init/knowledge_extractor.py +481 -0
  86. claude_mpm/cli/commands/mpm_init/modes.py +397 -0
  87. claude_mpm/cli/commands/mpm_init/prompts.py +722 -0
  88. claude_mpm/cli/commands/mpm_init_cli.py +396 -0
  89. claude_mpm/cli/commands/mpm_init_handler.py +195 -0
  90. claude_mpm/cli/commands/postmortem.py +401 -0
  91. claude_mpm/cli/commands/profile.py +276 -0
  92. claude_mpm/cli/commands/run.py +910 -488
  93. claude_mpm/cli/commands/search.py +458 -0
  94. claude_mpm/cli/commands/skill_source.py +694 -0
  95. claude_mpm/cli/commands/skills.py +1246 -0
  96. claude_mpm/cli/commands/summarize.py +413 -0
  97. claude_mpm/cli/commands/tickets.py +536 -53
  98. claude_mpm/cli/commands/uninstall.py +176 -0
  99. claude_mpm/cli/commands/upgrade.py +152 -0
  100. claude_mpm/cli/commands/verify.py +119 -0
  101. claude_mpm/cli/executor.py +297 -0
  102. claude_mpm/cli/helpers.py +105 -0
  103. claude_mpm/cli/interactive/__init__.py +21 -0
  104. claude_mpm/cli/interactive/agent_wizard.py +1947 -0
  105. claude_mpm/cli/interactive/skills_wizard.py +491 -0
  106. claude_mpm/cli/parser.py +87 -563
  107. claude_mpm/cli/parsers/__init__.py +35 -0
  108. claude_mpm/cli/parsers/agent_manager_parser.py +393 -0
  109. claude_mpm/cli/parsers/agent_source_parser.py +171 -0
  110. claude_mpm/cli/parsers/agents_parser.py +575 -0
  111. claude_mpm/cli/parsers/analyze_code_parser.py +170 -0
  112. claude_mpm/cli/parsers/analyze_parser.py +135 -0
  113. claude_mpm/cli/parsers/auto_configure_parser.py +120 -0
  114. claude_mpm/cli/parsers/base_parser.py +644 -0
  115. claude_mpm/cli/parsers/config_parser.py +208 -0
  116. claude_mpm/cli/parsers/configure_parser.py +138 -0
  117. claude_mpm/cli/parsers/dashboard_parser.py +113 -0
  118. claude_mpm/cli/parsers/debug_parser.py +319 -0
  119. claude_mpm/cli/parsers/local_deploy_parser.py +227 -0
  120. claude_mpm/cli/parsers/mcp_parser.py +195 -0
  121. claude_mpm/cli/parsers/memory_parser.py +138 -0
  122. claude_mpm/cli/parsers/monitor_parser.py +142 -0
  123. claude_mpm/cli/parsers/mpm_init_parser.py +311 -0
  124. claude_mpm/cli/parsers/profile_parser.py +147 -0
  125. claude_mpm/cli/parsers/run_parser.py +157 -0
  126. claude_mpm/cli/parsers/search_parser.py +245 -0
  127. claude_mpm/cli/parsers/skill_source_parser.py +169 -0
  128. claude_mpm/cli/parsers/skills_parser.py +277 -0
  129. claude_mpm/cli/parsers/source_parser.py +138 -0
  130. claude_mpm/cli/parsers/tickets_parser.py +203 -0
  131. claude_mpm/cli/shared/__init__.py +40 -0
  132. claude_mpm/cli/shared/argument_patterns.py +205 -0
  133. claude_mpm/cli/shared/base_command.py +242 -0
  134. claude_mpm/cli/shared/error_handling.py +242 -0
  135. claude_mpm/cli/shared/output_formatters.py +241 -0
  136. claude_mpm/cli/startup.py +1743 -0
  137. claude_mpm/cli/startup_display.py +480 -0
  138. claude_mpm/cli/startup_logging.py +839 -0
  139. claude_mpm/cli/utils.py +136 -47
  140. claude_mpm/cli_module/__init__.py +6 -6
  141. claude_mpm/cli_module/args.py +188 -140
  142. claude_mpm/cli_module/commands.py +79 -70
  143. claude_mpm/cli_module/migration_example.py +42 -64
  144. claude_mpm/commands/__init__.py +14 -0
  145. claude_mpm/commands/mpm-config.md +28 -0
  146. claude_mpm/commands/mpm-doctor.md +20 -0
  147. claude_mpm/commands/mpm-help.md +20 -0
  148. claude_mpm/commands/mpm-init.md +120 -0
  149. claude_mpm/commands/mpm-monitor.md +31 -0
  150. claude_mpm/commands/mpm-organize.md +120 -0
  151. claude_mpm/commands/mpm-postmortem.md +21 -0
  152. claude_mpm/commands/mpm-session-resume.md +30 -0
  153. claude_mpm/commands/mpm-status.md +20 -0
  154. claude_mpm/commands/mpm-ticket-view.md +109 -0
  155. claude_mpm/commands/mpm-version.md +20 -0
  156. claude_mpm/commands/mpm.md +31 -0
  157. claude_mpm/config/__init__.py +42 -2
  158. claude_mpm/config/agent_config.py +402 -0
  159. claude_mpm/config/agent_presets.py +488 -0
  160. claude_mpm/config/agent_sources.py +352 -0
  161. claude_mpm/config/experimental_features.py +217 -0
  162. claude_mpm/config/model_config.py +428 -0
  163. claude_mpm/config/paths.py +258 -0
  164. claude_mpm/config/skill_presets.py +392 -0
  165. claude_mpm/config/skill_sources.py +590 -0
  166. claude_mpm/config/socketio_config.py +125 -83
  167. claude_mpm/constants.py +132 -22
  168. claude_mpm/core/__init__.py +62 -36
  169. claude_mpm/core/agent_name_normalizer.py +71 -73
  170. claude_mpm/core/agent_registry.py +385 -492
  171. claude_mpm/core/agent_session_manager.py +81 -70
  172. claude_mpm/core/api_validator.py +330 -0
  173. claude_mpm/core/base_service.py +159 -122
  174. claude_mpm/core/cache.py +560 -0
  175. claude_mpm/core/claude_runner.py +696 -916
  176. claude_mpm/core/config.py +613 -122
  177. claude_mpm/core/config_aliases.py +74 -73
  178. claude_mpm/core/config_constants.py +314 -0
  179. claude_mpm/core/constants.py +361 -0
  180. claude_mpm/core/container.py +646 -104
  181. claude_mpm/core/enums.py +452 -0
  182. claude_mpm/core/error_handler.py +623 -0
  183. claude_mpm/core/exceptions.py +536 -0
  184. claude_mpm/core/factories.py +105 -109
  185. claude_mpm/core/file_utils.py +764 -0
  186. claude_mpm/core/framework/__init__.py +25 -0
  187. claude_mpm/core/framework/formatters/__init__.py +11 -0
  188. claude_mpm/core/framework/formatters/capability_generator.py +367 -0
  189. claude_mpm/core/framework/formatters/content_formatter.py +278 -0
  190. claude_mpm/core/framework/formatters/context_generator.py +185 -0
  191. claude_mpm/core/framework/loaders/__init__.py +13 -0
  192. claude_mpm/core/framework/loaders/agent_loader.py +213 -0
  193. claude_mpm/core/framework/loaders/file_loader.py +176 -0
  194. claude_mpm/core/framework/loaders/instruction_loader.py +222 -0
  195. claude_mpm/core/framework/loaders/packaged_loader.py +232 -0
  196. claude_mpm/core/framework/processors/__init__.py +11 -0
  197. claude_mpm/core/framework/processors/memory_processor.py +230 -0
  198. claude_mpm/core/framework/processors/metadata_processor.py +146 -0
  199. claude_mpm/core/framework/processors/template_processor.py +244 -0
  200. claude_mpm/core/framework_loader.py +485 -414
  201. claude_mpm/core/hook_error_memory.py +381 -0
  202. claude_mpm/core/hook_manager.py +246 -86
  203. claude_mpm/core/hook_performance_config.py +147 -0
  204. claude_mpm/core/injectable_service.py +72 -63
  205. claude_mpm/core/instruction_reinforcement_hook.py +267 -0
  206. claude_mpm/core/interactive_session.py +670 -0
  207. claude_mpm/core/interfaces.py +570 -164
  208. claude_mpm/core/lazy.py +467 -0
  209. claude_mpm/core/log_manager.py +707 -0
  210. claude_mpm/core/logger.py +295 -134
  211. claude_mpm/core/logging_config.py +474 -0
  212. claude_mpm/core/logging_utils.py +520 -0
  213. claude_mpm/core/minimal_framework_loader.py +24 -22
  214. claude_mpm/core/mixins.py +30 -29
  215. claude_mpm/core/oneshot_session.py +594 -0
  216. claude_mpm/core/optimized_agent_loader.py +479 -0
  217. claude_mpm/core/optimized_startup.py +554 -0
  218. claude_mpm/core/output_style_manager.py +483 -0
  219. claude_mpm/core/pm_hook_interceptor.py +197 -82
  220. claude_mpm/core/protocols/__init__.py +23 -0
  221. claude_mpm/core/protocols/runner_protocol.py +103 -0
  222. claude_mpm/core/protocols/session_protocol.py +131 -0
  223. claude_mpm/core/service_registry.py +153 -116
  224. claude_mpm/core/session_manager.py +179 -64
  225. claude_mpm/core/shared/__init__.py +17 -0
  226. claude_mpm/core/shared/config_loader.py +326 -0
  227. claude_mpm/core/shared/path_resolver.py +281 -0
  228. claude_mpm/core/shared/singleton_manager.py +221 -0
  229. claude_mpm/core/socketio_pool.py +400 -137
  230. claude_mpm/core/system_context.py +38 -0
  231. claude_mpm/core/tool_access_control.py +64 -57
  232. claude_mpm/core/types.py +307 -0
  233. claude_mpm/core/typing_utils.py +553 -0
  234. claude_mpm/core/unified_agent_registry.py +969 -0
  235. claude_mpm/core/unified_config.py +570 -0
  236. claude_mpm/core/unified_paths.py +941 -0
  237. claude_mpm/dashboard/__init__.py +12 -0
  238. claude_mpm/dashboard/api/simple_directory.py +261 -0
  239. claude_mpm/dashboard/static/svelte-build/_app/env.js +1 -0
  240. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.DWzvg0-y.css +1 -0
  241. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.ThTw9_ym.css +1 -0
  242. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/4TdZjIqw.js +1 -0
  243. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/5shd3_w0.js +24 -0
  244. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/B0uc0UOD.js +36 -0
  245. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/B7RN905-.js +1 -0
  246. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/B7xVLGWV.js +2 -0
  247. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BIF9m_hv.js +61 -0
  248. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BKjSRqUr.js +1 -0
  249. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BPYeabCQ.js +1 -0
  250. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BQaXIfA_.js +331 -0
  251. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BSNlmTZj.js +1 -0
  252. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Be7GpZd6.js +7 -0
  253. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Bh0LDWpI.js +145 -0
  254. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BofRWZRR.js +10 -0
  255. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BovzEFCE.js +30 -0
  256. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C30mlcqg.js +165 -0
  257. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C4B-KCzX.js +1 -0
  258. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C4JcI4KD.js +122 -0
  259. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CBBdVcY8.js +1 -0
  260. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CDuw-vjf.js +1 -0
  261. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C_Usid8X.js +15 -0
  262. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Cfqx1Qun.js +10 -0
  263. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CiIAseT4.js +128 -0
  264. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CmKTTxBW.js +1 -0
  265. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CnA0NrzZ.js +1 -0
  266. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Cs_tUR18.js +24 -0
  267. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Cu_Erd72.js +261 -0
  268. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CyWMqx4W.js +43 -0
  269. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CzZX-COe.js +220 -0
  270. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CzeYkLYB.js +65 -0
  271. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/D3k0OPJN.js +4 -0
  272. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/D9lljYKQ.js +1 -0
  273. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DGkLK5U1.js +267 -0
  274. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DI7hHRFL.js +1 -0
  275. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DLVjFsZ3.js +139 -0
  276. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DUrLdbGD.js +89 -0
  277. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DVp1hx9R.js +1 -0
  278. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DY1XQ8fi.js +2 -0
  279. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DZX00Y4g.js +1 -0
  280. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Da0KfYnO.js +1 -0
  281. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DaimHw_p.js +68 -0
  282. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Dfy6j1xT.js +323 -0
  283. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Dhb8PKl3.js +1 -0
  284. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Dle-35c7.js +64 -0
  285. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DmxopI1J.js +1 -0
  286. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DwBR2MJi.js +60 -0
  287. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/GYwsonyD.js +1 -0
  288. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Gi6I4Gst.js +1 -0
  289. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/NqQ1dWOy.js +1 -0
  290. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/RJiighC3.js +1 -0
  291. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Vzk33B_K.js +2 -0
  292. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/ZGh7QtNv.js +7 -0
  293. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/bT1r9zLR.js +1 -0
  294. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/bTOqqlTd.js +1 -0
  295. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/eNVUfhuA.js +1 -0
  296. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/iEWssX7S.js +162 -0
  297. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/sQeU3Y1z.js +1 -0
  298. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/uuIeMWc-.js +1 -0
  299. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/app.D6-I5TpK.js +2 -0
  300. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.NWzMBYRp.js +1 -0
  301. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/0.m1gL8KXf.js +1 -0
  302. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/1.CgNOuw-d.js +1 -0
  303. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.C0GcWctS.js +1 -0
  304. claude_mpm/dashboard/static/svelte-build/_app/version.json +1 -0
  305. claude_mpm/dashboard/static/svelte-build/favicon.svg +7 -0
  306. claude_mpm/dashboard/static/svelte-build/index.html +36 -0
  307. claude_mpm/dashboard-svelte/node_modules/katex/src/fonts/generate_fonts.py +58 -0
  308. claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/extract_tfms.py +114 -0
  309. claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/extract_ttfs.py +122 -0
  310. claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/format_json.py +28 -0
  311. claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/parse_tfm.py +211 -0
  312. claude_mpm/experimental/__init__.py +10 -0
  313. claude_mpm/experimental/cli_enhancements.py +104 -89
  314. claude_mpm/generators/__init__.py +1 -1
  315. claude_mpm/generators/agent_profile_generator.py +76 -66
  316. claude_mpm/hooks/__init__.py +37 -1
  317. claude_mpm/hooks/base_hook.py +37 -32
  318. claude_mpm/hooks/claude_hooks/__init__.py +1 -1
  319. claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-311.pyc +0 -0
  320. claude_mpm/hooks/claude_hooks/__pycache__/correlation_manager.cpython-311.pyc +0 -0
  321. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-311.pyc +0 -0
  322. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-311.pyc +0 -0
  323. claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-311.pyc +0 -0
  324. claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-311.pyc +0 -0
  325. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-311.pyc +0 -0
  326. claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-311.pyc +0 -0
  327. claude_mpm/hooks/claude_hooks/connection_pool.py +250 -0
  328. claude_mpm/hooks/claude_hooks/correlation_manager.py +60 -0
  329. claude_mpm/hooks/claude_hooks/event_handlers.py +888 -0
  330. claude_mpm/hooks/claude_hooks/hook_handler.py +652 -875
  331. claude_mpm/hooks/claude_hooks/hook_wrapper.sh +10 -7
  332. claude_mpm/hooks/claude_hooks/installer.py +806 -0
  333. claude_mpm/hooks/claude_hooks/memory_integration.py +249 -0
  334. claude_mpm/hooks/claude_hooks/response_tracking.py +412 -0
  335. claude_mpm/hooks/claude_hooks/services/__init__.py +15 -0
  336. claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-311.pyc +0 -0
  337. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-311.pyc +0 -0
  338. claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-311.pyc +0 -0
  339. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-311.pyc +0 -0
  340. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-311.pyc +0 -0
  341. claude_mpm/hooks/claude_hooks/services/connection_manager.py +229 -0
  342. claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +254 -0
  343. claude_mpm/hooks/claude_hooks/services/duplicate_detector.py +106 -0
  344. claude_mpm/hooks/claude_hooks/services/state_manager.py +284 -0
  345. claude_mpm/hooks/claude_hooks/services/subagent_processor.py +374 -0
  346. claude_mpm/hooks/claude_hooks/tool_analysis.py +224 -0
  347. claude_mpm/hooks/failure_learning/__init__.py +54 -0
  348. claude_mpm/hooks/failure_learning/failure_detection_hook.py +230 -0
  349. claude_mpm/hooks/failure_learning/fix_detection_hook.py +212 -0
  350. claude_mpm/hooks/failure_learning/learning_extraction_hook.py +281 -0
  351. claude_mpm/hooks/instruction_reinforcement.py +301 -0
  352. claude_mpm/hooks/kuzu_enrichment_hook.py +263 -0
  353. claude_mpm/hooks/kuzu_memory_hook.py +386 -0
  354. claude_mpm/hooks/kuzu_response_hook.py +179 -0
  355. claude_mpm/hooks/memory_integration_hook.py +201 -107
  356. claude_mpm/hooks/session_resume_hook.py +121 -0
  357. claude_mpm/hooks/templates/pre_tool_use_simple.py +78 -0
  358. claude_mpm/hooks/templates/pre_tool_use_template.py +323 -0
  359. claude_mpm/hooks/tool_call_interceptor.py +92 -76
  360. claude_mpm/hooks/validation_hooks.py +62 -54
  361. claude_mpm/init.py +518 -83
  362. claude_mpm/models/__init__.py +9 -9
  363. claude_mpm/models/agent_definition.py +40 -23
  364. claude_mpm/models/agent_session.py +538 -0
  365. claude_mpm/models/git_repository.py +198 -0
  366. claude_mpm/models/resume_log.py +340 -0
  367. claude_mpm/schemas/__init__.py +12 -0
  368. claude_mpm/scripts/__init__.py +15 -0
  369. claude_mpm/scripts/claude-hook-handler.sh +227 -0
  370. claude_mpm/scripts/launch_monitor.py +165 -0
  371. claude_mpm/scripts/mpm_doctor.py +322 -0
  372. claude_mpm/scripts/socketio_daemon.py +189 -200
  373. claude_mpm/scripts/start_activity_logging.py +91 -0
  374. claude_mpm/services/__init__.py +208 -39
  375. claude_mpm/services/agent_capabilities_service.py +266 -0
  376. claude_mpm/services/agents/__init__.py +89 -0
  377. claude_mpm/services/agents/agent_builder.py +514 -0
  378. claude_mpm/services/agents/agent_preset_service.py +238 -0
  379. claude_mpm/services/agents/agent_recommendation_service.py +278 -0
  380. claude_mpm/services/agents/agent_review_service.py +280 -0
  381. claude_mpm/services/agents/agent_selection_service.py +484 -0
  382. claude_mpm/services/agents/auto_config_manager.py +796 -0
  383. claude_mpm/services/agents/auto_deploy_index_parser.py +569 -0
  384. claude_mpm/services/agents/cache_git_manager.py +621 -0
  385. claude_mpm/services/agents/deployment/__init__.py +21 -0
  386. claude_mpm/services/agents/deployment/agent_config_provider.py +410 -0
  387. claude_mpm/services/agents/deployment/agent_configuration_manager.py +358 -0
  388. claude_mpm/services/agents/deployment/agent_definition_factory.py +80 -0
  389. claude_mpm/services/agents/deployment/agent_deployment.py +1037 -0
  390. claude_mpm/services/agents/deployment/agent_discovery_service.py +546 -0
  391. claude_mpm/services/agents/deployment/agent_environment_manager.py +288 -0
  392. claude_mpm/services/agents/deployment/agent_filesystem_manager.py +383 -0
  393. claude_mpm/services/agents/deployment/agent_format_converter.py +505 -0
  394. claude_mpm/services/agents/deployment/agent_frontmatter_validator.py +160 -0
  395. claude_mpm/services/agents/deployment/agent_lifecycle_manager.py +957 -0
  396. claude_mpm/services/agents/deployment/agent_metrics_collector.py +273 -0
  397. claude_mpm/services/agents/deployment/agent_operation_service.py +573 -0
  398. claude_mpm/services/agents/deployment/agent_record_service.py +418 -0
  399. claude_mpm/services/agents/deployment/agent_restore_handler.py +84 -0
  400. claude_mpm/services/agents/deployment/agent_state_service.py +381 -0
  401. claude_mpm/services/agents/deployment/agent_template_builder.py +1369 -0
  402. claude_mpm/services/agents/deployment/agent_validator.py +376 -0
  403. claude_mpm/services/agents/deployment/agent_version_manager.py +322 -0
  404. claude_mpm/services/{agent_versioning.py → agents/deployment/agent_versioning.py} +10 -13
  405. claude_mpm/services/agents/deployment/agents_directory_resolver.py +149 -0
  406. claude_mpm/services/agents/deployment/async_agent_deployment.py +768 -0
  407. claude_mpm/services/agents/deployment/base_agent_locator.py +132 -0
  408. claude_mpm/services/agents/deployment/config/__init__.py +13 -0
  409. claude_mpm/services/agents/deployment/config/deployment_config.py +181 -0
  410. claude_mpm/services/agents/deployment/config/deployment_config_manager.py +200 -0
  411. claude_mpm/services/agents/deployment/deployment_config_loader.py +178 -0
  412. claude_mpm/services/agents/deployment/deployment_results_manager.py +185 -0
  413. claude_mpm/services/agents/deployment/deployment_type_detector.py +120 -0
  414. claude_mpm/services/agents/deployment/deployment_wrapper.py +129 -0
  415. claude_mpm/services/agents/deployment/facade/__init__.py +18 -0
  416. claude_mpm/services/agents/deployment/facade/async_deployment_executor.py +159 -0
  417. claude_mpm/services/agents/deployment/facade/deployment_executor.py +70 -0
  418. claude_mpm/services/agents/deployment/facade/deployment_facade.py +269 -0
  419. claude_mpm/services/agents/deployment/facade/sync_deployment_executor.py +178 -0
  420. claude_mpm/services/agents/deployment/interface_adapter.py +226 -0
  421. claude_mpm/services/agents/deployment/lifecycle_health_checker.py +85 -0
  422. claude_mpm/services/agents/deployment/lifecycle_performance_tracker.py +100 -0
  423. claude_mpm/services/agents/deployment/local_template_deployment.py +362 -0
  424. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +1478 -0
  425. claude_mpm/services/agents/deployment/pipeline/__init__.py +32 -0
  426. claude_mpm/services/agents/deployment/pipeline/pipeline_builder.py +158 -0
  427. claude_mpm/services/agents/deployment/pipeline/pipeline_context.py +162 -0
  428. claude_mpm/services/agents/deployment/pipeline/pipeline_executor.py +169 -0
  429. claude_mpm/services/agents/deployment/pipeline/steps/__init__.py +19 -0
  430. claude_mpm/services/agents/deployment/pipeline/steps/agent_processing_step.py +240 -0
  431. claude_mpm/services/agents/deployment/pipeline/steps/base_step.py +110 -0
  432. claude_mpm/services/agents/deployment/pipeline/steps/configuration_step.py +80 -0
  433. claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +92 -0
  434. claude_mpm/services/agents/deployment/pipeline/steps/validation_step.py +101 -0
  435. claude_mpm/services/agents/deployment/processors/__init__.py +15 -0
  436. claude_mpm/services/agents/deployment/processors/agent_deployment_context.py +102 -0
  437. claude_mpm/services/agents/deployment/processors/agent_deployment_result.py +235 -0
  438. claude_mpm/services/agents/deployment/processors/agent_processor.py +269 -0
  439. claude_mpm/services/agents/deployment/refactored_agent_deployment_service.py +311 -0
  440. claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +862 -0
  441. claude_mpm/services/agents/deployment/results/__init__.py +13 -0
  442. claude_mpm/services/agents/deployment/results/deployment_metrics.py +200 -0
  443. claude_mpm/services/agents/deployment/results/deployment_result_builder.py +249 -0
  444. claude_mpm/services/agents/deployment/single_agent_deployer.py +315 -0
  445. claude_mpm/services/agents/deployment/strategies/__init__.py +25 -0
  446. claude_mpm/services/agents/deployment/strategies/base_strategy.py +113 -0
  447. claude_mpm/services/agents/deployment/strategies/project_strategy.py +148 -0
  448. claude_mpm/services/agents/deployment/strategies/strategy_selector.py +117 -0
  449. claude_mpm/services/agents/deployment/strategies/system_strategy.py +131 -0
  450. claude_mpm/services/agents/deployment/strategies/user_strategy.py +130 -0
  451. claude_mpm/services/agents/deployment/system_instructions_deployer.py +228 -0
  452. claude_mpm/services/agents/deployment/validation/__init__.py +21 -0
  453. claude_mpm/services/agents/deployment/validation/agent_validator.py +323 -0
  454. claude_mpm/services/agents/deployment/validation/deployment_validator.py +238 -0
  455. claude_mpm/services/agents/deployment/validation/template_validator.py +319 -0
  456. claude_mpm/services/agents/deployment/validation/validation_result.py +214 -0
  457. claude_mpm/services/agents/git_source_manager.py +682 -0
  458. claude_mpm/services/agents/loading/__init__.py +11 -0
  459. claude_mpm/services/{agent_profile_loader.py → agents/loading/agent_profile_loader.py} +306 -228
  460. claude_mpm/services/{base_agent_manager.py → agents/loading/base_agent_manager.py} +106 -91
  461. claude_mpm/services/agents/loading/framework_agent_loader.py +433 -0
  462. claude_mpm/services/agents/local_template_manager.py +784 -0
  463. claude_mpm/services/agents/management/__init__.py +9 -0
  464. claude_mpm/services/{agent_capabilities_generator.py → agents/management/agent_capabilities_generator.py} +92 -69
  465. claude_mpm/services/{agent_management_service.py → agents/management/agent_management_service.py} +219 -168
  466. claude_mpm/services/agents/memory/__init__.py +22 -0
  467. claude_mpm/services/agents/memory/agent_memory_manager.py +784 -0
  468. claude_mpm/services/{agent_persistence_service.py → agents/memory/agent_persistence_service.py} +20 -18
  469. claude_mpm/services/agents/memory/content_manager.py +470 -0
  470. claude_mpm/services/agents/memory/memory_categorization_service.py +167 -0
  471. claude_mpm/services/agents/memory/memory_file_service.py +129 -0
  472. claude_mpm/services/agents/memory/memory_format_service.py +201 -0
  473. claude_mpm/services/agents/memory/memory_limits_service.py +101 -0
  474. claude_mpm/services/agents/memory/template_generator.py +83 -0
  475. claude_mpm/services/agents/observers.py +547 -0
  476. claude_mpm/services/agents/recommender.py +617 -0
  477. claude_mpm/services/agents/registry/__init__.py +30 -0
  478. claude_mpm/services/agents/registry/deployed_agent_discovery.py +273 -0
  479. claude_mpm/services/{agent_modification_tracker.py → agents/registry/modification_tracker.py} +370 -295
  480. claude_mpm/services/agents/single_tier_deployment_service.py +696 -0
  481. claude_mpm/services/agents/sources/__init__.py +13 -0
  482. claude_mpm/services/agents/sources/agent_sync_state.py +516 -0
  483. claude_mpm/services/agents/sources/git_source_sync_service.py +1202 -0
  484. claude_mpm/services/agents/startup_sync.py +259 -0
  485. claude_mpm/services/agents/toolchain_detector.py +478 -0
  486. claude_mpm/services/analysis/__init__.py +35 -0
  487. claude_mpm/services/analysis/clone_detector.py +1030 -0
  488. claude_mpm/services/analysis/postmortem_reporter.py +474 -0
  489. claude_mpm/services/analysis/postmortem_service.py +765 -0
  490. claude_mpm/services/async_session_logger.py +665 -0
  491. claude_mpm/services/claude_session_logger.py +321 -0
  492. claude_mpm/services/cli/__init__.py +18 -0
  493. claude_mpm/services/cli/agent_cleanup_service.py +408 -0
  494. claude_mpm/services/cli/agent_dependency_service.py +395 -0
  495. claude_mpm/services/cli/agent_listing_service.py +463 -0
  496. claude_mpm/services/cli/agent_output_formatter.py +605 -0
  497. claude_mpm/services/cli/agent_validation_service.py +590 -0
  498. claude_mpm/services/cli/memory_crud_service.py +622 -0
  499. claude_mpm/services/cli/memory_output_formatter.py +604 -0
  500. claude_mpm/services/cli/resume_service.py +617 -0
  501. claude_mpm/services/cli/session_manager.py +604 -0
  502. claude_mpm/services/cli/session_pause_manager.py +504 -0
  503. claude_mpm/services/cli/session_resume_helper.py +372 -0
  504. claude_mpm/services/cli/startup_checker.py +362 -0
  505. claude_mpm/services/cli/unified_dashboard_manager.py +439 -0
  506. claude_mpm/services/command_deployment_service.py +446 -0
  507. claude_mpm/services/command_handler_service.py +221 -0
  508. claude_mpm/services/communication/__init__.py +22 -0
  509. claude_mpm/services/core/__init__.py +108 -0
  510. claude_mpm/services/core/base.py +269 -0
  511. claude_mpm/services/core/cache_manager.py +309 -0
  512. claude_mpm/services/core/interfaces/__init__.py +273 -0
  513. claude_mpm/services/core/interfaces/agent.py +514 -0
  514. claude_mpm/services/core/interfaces/communication.py +316 -0
  515. claude_mpm/services/core/interfaces/health.py +169 -0
  516. claude_mpm/services/core/interfaces/infrastructure.py +357 -0
  517. claude_mpm/services/core/interfaces/model.py +281 -0
  518. claude_mpm/services/core/interfaces/process.py +372 -0
  519. claude_mpm/services/core/interfaces/project.py +121 -0
  520. claude_mpm/services/core/interfaces/restart.py +307 -0
  521. claude_mpm/services/core/interfaces/service.py +405 -0
  522. claude_mpm/services/core/interfaces/stability.py +260 -0
  523. claude_mpm/services/core/interfaces.py +81 -0
  524. claude_mpm/services/core/memory_manager.py +682 -0
  525. claude_mpm/services/core/models/__init__.py +70 -0
  526. claude_mpm/services/core/models/agent_config.py +384 -0
  527. claude_mpm/services/core/models/health.py +162 -0
  528. claude_mpm/services/core/models/process.py +239 -0
  529. claude_mpm/services/core/models/restart.py +302 -0
  530. claude_mpm/services/core/models/stability.py +264 -0
  531. claude_mpm/services/core/models/toolchain.py +306 -0
  532. claude_mpm/services/core/path_resolver.py +517 -0
  533. claude_mpm/services/core/service_container.py +520 -0
  534. claude_mpm/services/core/service_interfaces.py +436 -0
  535. claude_mpm/services/diagnostics/__init__.py +18 -0
  536. claude_mpm/services/diagnostics/checks/__init__.py +38 -0
  537. claude_mpm/services/diagnostics/checks/agent_check.py +370 -0
  538. claude_mpm/services/diagnostics/checks/agent_sources_check.py +577 -0
  539. claude_mpm/services/diagnostics/checks/base_check.py +60 -0
  540. claude_mpm/services/diagnostics/checks/claude_code_check.py +270 -0
  541. claude_mpm/services/diagnostics/checks/common_issues_check.py +363 -0
  542. claude_mpm/services/diagnostics/checks/configuration_check.py +306 -0
  543. claude_mpm/services/diagnostics/checks/filesystem_check.py +233 -0
  544. claude_mpm/services/diagnostics/checks/installation_check.py +520 -0
  545. claude_mpm/services/diagnostics/checks/instructions_check.py +415 -0
  546. claude_mpm/services/diagnostics/checks/mcp_check.py +330 -0
  547. claude_mpm/services/diagnostics/checks/mcp_services_check.py +1058 -0
  548. claude_mpm/services/diagnostics/checks/monitor_check.py +281 -0
  549. claude_mpm/services/diagnostics/checks/skill_sources_check.py +587 -0
  550. claude_mpm/services/diagnostics/checks/startup_log_check.py +319 -0
  551. claude_mpm/services/diagnostics/diagnostic_runner.py +286 -0
  552. claude_mpm/services/diagnostics/doctor_reporter.py +578 -0
  553. claude_mpm/services/diagnostics/models.py +138 -0
  554. claude_mpm/services/event_aggregator.py +582 -0
  555. claude_mpm/services/event_bus/__init__.py +18 -0
  556. claude_mpm/services/event_bus/config.py +186 -0
  557. claude_mpm/services/event_bus/direct_relay.py +312 -0
  558. claude_mpm/services/event_bus/event_bus.py +396 -0
  559. claude_mpm/services/event_bus/relay.py +326 -0
  560. claude_mpm/services/events/__init__.py +44 -0
  561. claude_mpm/services/events/consumers/__init__.py +18 -0
  562. claude_mpm/services/events/consumers/dead_letter.py +306 -0
  563. claude_mpm/services/events/consumers/logging.py +184 -0
  564. claude_mpm/services/events/consumers/metrics.py +241 -0
  565. claude_mpm/services/events/consumers/socketio.py +377 -0
  566. claude_mpm/services/events/core.py +480 -0
  567. claude_mpm/services/events/interfaces.py +214 -0
  568. claude_mpm/services/events/producers/__init__.py +14 -0
  569. claude_mpm/services/events/producers/hook.py +269 -0
  570. claude_mpm/services/events/producers/system.py +329 -0
  571. claude_mpm/services/exceptions.py +433 -353
  572. claude_mpm/services/framework_claude_md_generator/__init__.py +81 -80
  573. claude_mpm/services/framework_claude_md_generator/content_assembler.py +74 -67
  574. claude_mpm/services/framework_claude_md_generator/content_validator.py +66 -62
  575. claude_mpm/services/framework_claude_md_generator/deployment_manager.py +82 -60
  576. claude_mpm/services/framework_claude_md_generator/section_generators/__init__.py +36 -37
  577. claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +41 -40
  578. claude_mpm/services/framework_claude_md_generator/section_generators/claude_pm_init.py +15 -15
  579. claude_mpm/services/framework_claude_md_generator/section_generators/core_responsibilities.py +5 -4
  580. claude_mpm/services/framework_claude_md_generator/section_generators/delegation_constraints.py +4 -3
  581. claude_mpm/services/framework_claude_md_generator/section_generators/environment_config.py +4 -3
  582. claude_mpm/services/framework_claude_md_generator/section_generators/footer.py +6 -5
  583. claude_mpm/services/framework_claude_md_generator/section_generators/header.py +8 -7
  584. claude_mpm/services/framework_claude_md_generator/section_generators/orchestration_principles.py +5 -4
  585. claude_mpm/services/framework_claude_md_generator/section_generators/role_designation.py +6 -5
  586. claude_mpm/services/framework_claude_md_generator/section_generators/subprocess_validation.py +9 -8
  587. claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +26 -30
  588. claude_mpm/services/framework_claude_md_generator/section_generators/troubleshooting.py +6 -5
  589. claude_mpm/services/framework_claude_md_generator/section_manager.py +28 -27
  590. claude_mpm/services/framework_claude_md_generator/version_manager.py +31 -30
  591. claude_mpm/services/git/__init__.py +21 -0
  592. claude_mpm/services/git/git_operations_service.py +579 -0
  593. claude_mpm/services/github/__init__.py +21 -0
  594. claude_mpm/services/github/github_cli_service.py +397 -0
  595. claude_mpm/services/hook_installer_service.py +506 -0
  596. claude_mpm/services/hook_service.py +159 -111
  597. claude_mpm/services/infrastructure/__init__.py +52 -0
  598. claude_mpm/services/infrastructure/context_preservation.py +569 -0
  599. claude_mpm/services/infrastructure/daemon_manager.py +279 -0
  600. claude_mpm/services/infrastructure/logging.py +209 -0
  601. claude_mpm/services/infrastructure/monitoring/__init__.py +39 -0
  602. claude_mpm/services/infrastructure/monitoring/aggregator.py +432 -0
  603. claude_mpm/services/infrastructure/monitoring/base.py +122 -0
  604. claude_mpm/services/infrastructure/monitoring/legacy.py +203 -0
  605. claude_mpm/services/infrastructure/monitoring/network.py +219 -0
  606. claude_mpm/services/infrastructure/monitoring/process.py +343 -0
  607. claude_mpm/services/infrastructure/monitoring/resources.py +244 -0
  608. claude_mpm/services/infrastructure/monitoring/service.py +368 -0
  609. claude_mpm/services/infrastructure/monitoring.py +71 -0
  610. claude_mpm/services/infrastructure/resume_log_generator.py +439 -0
  611. claude_mpm/services/instructions/__init__.py +9 -0
  612. claude_mpm/services/instructions/instruction_cache_service.py +374 -0
  613. claude_mpm/services/local_ops/__init__.py +155 -0
  614. claude_mpm/services/local_ops/crash_detector.py +257 -0
  615. claude_mpm/services/local_ops/health_checks/__init__.py +26 -0
  616. claude_mpm/services/local_ops/health_checks/http_check.py +224 -0
  617. claude_mpm/services/local_ops/health_checks/process_check.py +236 -0
  618. claude_mpm/services/local_ops/health_checks/resource_check.py +255 -0
  619. claude_mpm/services/local_ops/health_manager.py +427 -0
  620. claude_mpm/services/local_ops/log_monitor.py +396 -0
  621. claude_mpm/services/local_ops/memory_leak_detector.py +294 -0
  622. claude_mpm/services/local_ops/process_manager.py +595 -0
  623. claude_mpm/services/local_ops/resource_monitor.py +331 -0
  624. claude_mpm/services/local_ops/restart_manager.py +401 -0
  625. claude_mpm/services/local_ops/restart_policy.py +387 -0
  626. claude_mpm/services/local_ops/state_manager.py +372 -0
  627. claude_mpm/services/local_ops/unified_manager.py +600 -0
  628. claude_mpm/services/mcp_config_manager.py +1542 -0
  629. claude_mpm/services/mcp_service_verifier.py +732 -0
  630. claude_mpm/services/memory/__init__.py +19 -0
  631. claude_mpm/services/{memory_builder.py → memory/builder.py} +465 -373
  632. claude_mpm/services/memory/cache/__init__.py +14 -0
  633. claude_mpm/services/{shared_prompt_cache.py → memory/cache/shared_prompt_cache.py} +237 -200
  634. claude_mpm/services/memory/cache/simple_cache.py +331 -0
  635. claude_mpm/services/memory/failure_tracker.py +578 -0
  636. claude_mpm/services/memory/indexed_memory.py +648 -0
  637. claude_mpm/services/{memory_optimizer.py → memory/optimizer.py} +272 -243
  638. claude_mpm/services/memory/router.py +951 -0
  639. claude_mpm/services/memory_hook_service.py +470 -0
  640. claude_mpm/services/model/__init__.py +147 -0
  641. claude_mpm/services/model/base_provider.py +365 -0
  642. claude_mpm/services/model/claude_provider.py +412 -0
  643. claude_mpm/services/model/model_router.py +452 -0
  644. claude_mpm/services/model/ollama_provider.py +415 -0
  645. claude_mpm/services/monitor/__init__.py +20 -0
  646. claude_mpm/services/monitor/daemon.py +698 -0
  647. claude_mpm/services/monitor/daemon_manager.py +1076 -0
  648. claude_mpm/services/monitor/event_emitter.py +350 -0
  649. claude_mpm/services/monitor/handlers/__init__.py +21 -0
  650. claude_mpm/services/monitor/handlers/code_analysis.py +332 -0
  651. claude_mpm/services/monitor/handlers/dashboard.py +299 -0
  652. claude_mpm/services/monitor/handlers/file.py +264 -0
  653. claude_mpm/services/monitor/handlers/hooks.py +512 -0
  654. claude_mpm/services/monitor/management/__init__.py +18 -0
  655. claude_mpm/services/monitor/management/health.py +124 -0
  656. claude_mpm/services/monitor/management/lifecycle.py +730 -0
  657. claude_mpm/services/monitor/server.py +1493 -0
  658. claude_mpm/services/monitor_build_service.py +349 -0
  659. claude_mpm/services/native_agent_converter.py +356 -0
  660. claude_mpm/services/orphan_detection.py +786 -0
  661. claude_mpm/services/pm_skills_deployer.py +707 -0
  662. claude_mpm/services/port_manager.py +597 -0
  663. claude_mpm/services/pr/__init__.py +14 -0
  664. claude_mpm/services/pr/pr_template_service.py +329 -0
  665. claude_mpm/services/profile_manager.py +337 -0
  666. claude_mpm/services/project/__init__.py +44 -0
  667. claude_mpm/services/{project_analyzer.py → project/analyzer.py} +541 -291
  668. claude_mpm/services/project/analyzer_v2.py +566 -0
  669. claude_mpm/services/project/architecture_analyzer.py +461 -0
  670. claude_mpm/services/project/archive_manager.py +1045 -0
  671. claude_mpm/services/project/dependency_analyzer.py +462 -0
  672. claude_mpm/services/project/detection_strategies.py +719 -0
  673. claude_mpm/services/project/documentation_manager.py +554 -0
  674. claude_mpm/services/project/enhanced_analyzer.py +572 -0
  675. claude_mpm/services/project/language_analyzer.py +265 -0
  676. claude_mpm/services/project/metrics_collector.py +407 -0
  677. claude_mpm/services/project/project_organizer.py +1009 -0
  678. claude_mpm/services/project/registry.py +636 -0
  679. claude_mpm/services/project/toolchain_analyzer.py +583 -0
  680. claude_mpm/services/project_port_allocator.py +596 -0
  681. claude_mpm/services/recovery_manager.py +293 -240
  682. claude_mpm/services/response_tracker.py +267 -0
  683. claude_mpm/services/runner_configuration_service.py +605 -0
  684. claude_mpm/services/self_upgrade_service.py +608 -0
  685. claude_mpm/services/session_management_service.py +314 -0
  686. claude_mpm/services/session_manager.py +380 -0
  687. claude_mpm/services/shared/__init__.py +21 -0
  688. claude_mpm/services/shared/async_service_base.py +216 -0
  689. claude_mpm/services/shared/config_service_base.py +301 -0
  690. claude_mpm/services/shared/lifecycle_service_base.py +308 -0
  691. claude_mpm/services/shared/manager_base.py +315 -0
  692. claude_mpm/services/shared/service_factory.py +309 -0
  693. claude_mpm/services/skills/__init__.py +21 -0
  694. claude_mpm/services/skills/git_skill_source_manager.py +1324 -0
  695. claude_mpm/services/skills/selective_skill_deployer.py +744 -0
  696. claude_mpm/services/skills/skill_discovery_service.py +568 -0
  697. claude_mpm/services/skills/skill_to_agent_mapper.py +406 -0
  698. claude_mpm/services/skills_config.py +547 -0
  699. claude_mpm/services/skills_deployer.py +1168 -0
  700. claude_mpm/services/socketio/__init__.py +25 -0
  701. claude_mpm/services/socketio/client_proxy.py +229 -0
  702. claude_mpm/services/socketio/dashboard_server.py +362 -0
  703. claude_mpm/services/socketio/event_normalizer.py +798 -0
  704. claude_mpm/services/socketio/handlers/__init__.py +30 -0
  705. claude_mpm/services/socketio/handlers/base.py +136 -0
  706. claude_mpm/services/socketio/handlers/code_analysis.py +682 -0
  707. claude_mpm/services/socketio/handlers/connection.py +643 -0
  708. claude_mpm/services/socketio/handlers/connection_handler.py +333 -0
  709. claude_mpm/services/socketio/handlers/file.py +263 -0
  710. claude_mpm/services/socketio/handlers/git.py +962 -0
  711. claude_mpm/services/socketio/handlers/hook.py +211 -0
  712. claude_mpm/services/socketio/handlers/memory.py +26 -0
  713. claude_mpm/services/socketio/handlers/project.py +24 -0
  714. claude_mpm/services/socketio/handlers/registry.py +214 -0
  715. claude_mpm/services/socketio/migration_utils.py +343 -0
  716. claude_mpm/services/socketio/monitor_client.py +364 -0
  717. claude_mpm/services/socketio/server/__init__.py +18 -0
  718. claude_mpm/services/socketio/server/broadcaster.py +569 -0
  719. claude_mpm/services/socketio/server/connection_manager.py +579 -0
  720. claude_mpm/services/socketio/server/core.py +1079 -0
  721. claude_mpm/services/socketio/server/eventbus_integration.py +245 -0
  722. claude_mpm/services/socketio/server/main.py +501 -0
  723. claude_mpm/services/socketio_client_manager.py +173 -143
  724. claude_mpm/services/socketio_server.py +38 -1657
  725. claude_mpm/services/subprocess_launcher_service.py +322 -0
  726. claude_mpm/services/system_instructions_service.py +270 -0
  727. claude_mpm/services/ticket_manager.py +25 -209
  728. claude_mpm/services/ticket_services/__init__.py +26 -0
  729. claude_mpm/services/ticket_services/crud_service.py +328 -0
  730. claude_mpm/services/ticket_services/formatter_service.py +290 -0
  731. claude_mpm/services/ticket_services/search_service.py +324 -0
  732. claude_mpm/services/ticket_services/validation_service.py +303 -0
  733. claude_mpm/services/ticket_services/workflow_service.py +244 -0
  734. claude_mpm/services/unified/__init__.py +65 -0
  735. claude_mpm/services/unified/analyzer_strategies/__init__.py +44 -0
  736. claude_mpm/services/unified/analyzer_strategies/code_analyzer.py +518 -0
  737. claude_mpm/services/unified/analyzer_strategies/dependency_analyzer.py +680 -0
  738. claude_mpm/services/unified/analyzer_strategies/performance_analyzer.py +900 -0
  739. claude_mpm/services/unified/analyzer_strategies/security_analyzer.py +745 -0
  740. claude_mpm/services/unified/analyzer_strategies/structure_analyzer.py +733 -0
  741. claude_mpm/services/unified/config_strategies/__init__.py +175 -0
  742. claude_mpm/services/unified/config_strategies/config_schema.py +731 -0
  743. claude_mpm/services/unified/config_strategies/context_strategy.py +747 -0
  744. claude_mpm/services/unified/config_strategies/error_handling_strategy.py +1005 -0
  745. claude_mpm/services/unified/config_strategies/file_loader_strategy.py +881 -0
  746. claude_mpm/services/unified/config_strategies/unified_config_service.py +823 -0
  747. claude_mpm/services/unified/config_strategies/validation_strategy.py +1148 -0
  748. claude_mpm/services/unified/deployment_strategies/__init__.py +97 -0
  749. claude_mpm/services/unified/deployment_strategies/base.py +553 -0
  750. claude_mpm/services/unified/deployment_strategies/cloud_strategies.py +573 -0
  751. claude_mpm/services/unified/deployment_strategies/local.py +607 -0
  752. claude_mpm/services/unified/deployment_strategies/utils.py +667 -0
  753. claude_mpm/services/unified/deployment_strategies/vercel.py +471 -0
  754. claude_mpm/services/unified/interfaces.py +475 -0
  755. claude_mpm/services/unified/migration.py +509 -0
  756. claude_mpm/services/unified/strategies.py +534 -0
  757. claude_mpm/services/unified/unified_analyzer.py +542 -0
  758. claude_mpm/services/unified/unified_config.py +691 -0
  759. claude_mpm/services/unified/unified_deployment.py +466 -0
  760. claude_mpm/services/utility_service.py +280 -0
  761. claude_mpm/services/version_control/__init__.py +34 -37
  762. claude_mpm/services/version_control/branch_strategy.py +26 -17
  763. claude_mpm/services/version_control/conflict_resolution.py +52 -36
  764. claude_mpm/services/version_control/git_operations.py +183 -49
  765. claude_mpm/services/version_control/semantic_versioning.py +172 -61
  766. claude_mpm/services/version_control/version_parser.py +546 -0
  767. claude_mpm/services/version_service.py +379 -0
  768. claude_mpm/services/visualization/__init__.py +15 -0
  769. claude_mpm/services/visualization/mermaid_generator.py +937 -0
  770. claude_mpm/skills/__init__.py +42 -0
  771. claude_mpm/skills/agent_skills_injector.py +324 -0
  772. claude_mpm/skills/bundled/LICENSE_ATTRIBUTIONS.md +79 -0
  773. claude_mpm/skills/bundled/__init__.py +6 -0
  774. claude_mpm/skills/bundled/api-documentation.md +393 -0
  775. claude_mpm/skills/bundled/async-testing.md +571 -0
  776. claude_mpm/skills/bundled/code-review.md +143 -0
  777. claude_mpm/skills/bundled/database-migration.md +199 -0
  778. claude_mpm/skills/bundled/docker-containerization.md +194 -0
  779. claude_mpm/skills/bundled/express-local-dev.md +1429 -0
  780. claude_mpm/skills/bundled/fastapi-local-dev.md +1199 -0
  781. claude_mpm/skills/bundled/git-workflow.md +414 -0
  782. claude_mpm/skills/bundled/imagemagick.md +204 -0
  783. claude_mpm/skills/bundled/infrastructure/env-manager/scripts/validate_env.py +576 -0
  784. claude_mpm/skills/bundled/json-data-handling.md +223 -0
  785. claude_mpm/skills/bundled/main/mcp-builder/scripts/connections.py +157 -0
  786. claude_mpm/skills/bundled/main/mcp-builder/scripts/evaluation.py +425 -0
  787. claude_mpm/skills/bundled/main/skill-creator/scripts/init_skill.py +303 -0
  788. claude_mpm/skills/bundled/main/skill-creator/scripts/package_skill.py +113 -0
  789. claude_mpm/skills/bundled/main/skill-creator/scripts/quick_validate.py +72 -0
  790. claude_mpm/skills/bundled/nextjs-local-dev.md +807 -0
  791. claude_mpm/skills/bundled/pdf.md +141 -0
  792. claude_mpm/skills/bundled/performance-profiling.md +573 -0
  793. claude_mpm/skills/bundled/refactoring-patterns.md +180 -0
  794. claude_mpm/skills/bundled/security-scanning.md +439 -0
  795. claude_mpm/skills/bundled/systematic-debugging.md +473 -0
  796. claude_mpm/skills/bundled/test-driven-development.md +378 -0
  797. claude_mpm/skills/bundled/testing/webapp-testing/examples/console_logging.py +35 -0
  798. claude_mpm/skills/bundled/testing/webapp-testing/examples/element_discovery.py +44 -0
  799. claude_mpm/skills/bundled/testing/webapp-testing/examples/static_html_automation.py +34 -0
  800. claude_mpm/skills/bundled/testing/webapp-testing/scripts/with_server.py +129 -0
  801. claude_mpm/skills/bundled/vite-local-dev.md +1061 -0
  802. claude_mpm/skills/bundled/web-performance-optimization.md +2305 -0
  803. claude_mpm/skills/bundled/xlsx.md +157 -0
  804. claude_mpm/skills/registry.py +286 -0
  805. claude_mpm/skills/skill_manager.py +405 -0
  806. claude_mpm/skills/skills_registry.py +347 -0
  807. claude_mpm/skills/skills_service.py +739 -0
  808. claude_mpm/storage/__init__.py +9 -0
  809. claude_mpm/storage/state_storage.py +546 -0
  810. claude_mpm/templates/.pre-commit-config.yaml +112 -0
  811. claude_mpm/templates/questions/__init__.py +38 -0
  812. claude_mpm/templates/questions/base.py +193 -0
  813. claude_mpm/templates/questions/pr_strategy.py +311 -0
  814. claude_mpm/templates/questions/project_init.py +385 -0
  815. claude_mpm/templates/questions/ticket_mgmt.py +394 -0
  816. claude_mpm/ticket_wrapper.py +2 -2
  817. claude_mpm/tools/__init__.py +10 -0
  818. claude_mpm/tools/__main__.py +208 -0
  819. claude_mpm/tools/code_tree_analyzer/__init__.py +45 -0
  820. claude_mpm/tools/code_tree_analyzer/analysis.py +299 -0
  821. claude_mpm/tools/code_tree_analyzer/cache.py +131 -0
  822. claude_mpm/tools/code_tree_analyzer/core.py +380 -0
  823. claude_mpm/tools/code_tree_analyzer/discovery.py +403 -0
  824. claude_mpm/tools/code_tree_analyzer/events.py +168 -0
  825. claude_mpm/tools/code_tree_analyzer/gitignore.py +308 -0
  826. claude_mpm/tools/code_tree_analyzer/models.py +39 -0
  827. claude_mpm/tools/code_tree_analyzer/multilang_analyzer.py +224 -0
  828. claude_mpm/tools/code_tree_analyzer/python_analyzer.py +284 -0
  829. claude_mpm/tools/code_tree_builder.py +631 -0
  830. claude_mpm/tools/code_tree_events.py +420 -0
  831. claude_mpm/tools/socketio_debug.py +671 -0
  832. claude_mpm/utils/__init__.py +8 -8
  833. claude_mpm/utils/agent_dependency_loader.py +1090 -0
  834. claude_mpm/utils/agent_filters.py +261 -0
  835. claude_mpm/utils/common.py +544 -0
  836. claude_mpm/utils/config_manager.py +168 -126
  837. claude_mpm/utils/console.py +11 -0
  838. claude_mpm/utils/database_connector.py +298 -0
  839. claude_mpm/utils/dependency_cache.py +373 -0
  840. claude_mpm/utils/dependency_manager.py +60 -59
  841. claude_mpm/utils/dependency_strategies.py +381 -0
  842. claude_mpm/utils/display_helper.py +260 -0
  843. claude_mpm/utils/environment_context.py +313 -0
  844. claude_mpm/utils/error_handler.py +78 -66
  845. claude_mpm/utils/file_utils.py +305 -0
  846. claude_mpm/utils/framework_detection.py +12 -11
  847. claude_mpm/utils/git_analyzer.py +407 -0
  848. claude_mpm/utils/gitignore.py +244 -0
  849. claude_mpm/utils/import_migration_example.py +12 -60
  850. claude_mpm/utils/imports.py +48 -45
  851. claude_mpm/utils/log_cleanup.py +627 -0
  852. claude_mpm/utils/migration.py +372 -0
  853. claude_mpm/utils/path_operations.py +110 -104
  854. claude_mpm/utils/progress.py +387 -0
  855. claude_mpm/utils/robust_installer.py +823 -0
  856. claude_mpm/utils/session_logging.py +121 -0
  857. claude_mpm/utils/structured_questions.py +619 -0
  858. claude_mpm/utils/subprocess_utils.py +343 -0
  859. claude_mpm/validation/__init__.py +1 -1
  860. claude_mpm/validation/agent_validator.py +214 -108
  861. claude_mpm/validation/frontmatter_validator.py +252 -0
  862. claude_mpm-5.4.55.dist-info/METADATA +999 -0
  863. claude_mpm-5.4.55.dist-info/RECORD +868 -0
  864. {claude_mpm-3.4.10.dist-info → claude_mpm-5.4.55.dist-info}/entry_points.txt +1 -3
  865. claude_mpm-5.4.55.dist-info/licenses/LICENSE +94 -0
  866. claude_mpm-5.4.55.dist-info/licenses/LICENSE-FAQ.md +153 -0
  867. claude_mpm/agents/BASE_AGENT_TEMPLATE.md +0 -88
  868. claude_mpm/agents/INSTRUCTIONS.md +0 -352
  869. claude_mpm/agents/backups/INSTRUCTIONS.md +0 -352
  870. claude_mpm/agents/base_agent_loader.py +0 -529
  871. claude_mpm/agents/schema/agent_schema.json +0 -314
  872. claude_mpm/agents/templates/.claude-mpm/memories/README.md +0 -36
  873. claude_mpm/agents/templates/backup/data_engineer_agent_20250726_234551.json +0 -46
  874. claude_mpm/agents/templates/backup/documentation_agent_20250726_234551.json +0 -45
  875. claude_mpm/agents/templates/backup/engineer_agent_20250726_234551.json +0 -49
  876. claude_mpm/agents/templates/backup/ops_agent_20250726_234551.json +0 -46
  877. claude_mpm/agents/templates/backup/qa_agent_20250726_234551.json +0 -45
  878. claude_mpm/agents/templates/backup/research_agent_20250726_234551.json +0 -49
  879. claude_mpm/agents/templates/backup/security_agent_20250726_234551.json +0 -46
  880. claude_mpm/agents/templates/backup/version_control_agent_20250726_234551.json +0 -46
  881. claude_mpm/agents/templates/data_engineer.json +0 -110
  882. claude_mpm/agents/templates/documentation.json +0 -109
  883. claude_mpm/agents/templates/engineer.json +0 -113
  884. claude_mpm/agents/templates/ops.json +0 -109
  885. claude_mpm/agents/templates/pm.json +0 -25
  886. claude_mpm/agents/templates/qa.json +0 -111
  887. claude_mpm/agents/templates/research.json +0 -65
  888. claude_mpm/agents/templates/security.json +0 -113
  889. claude_mpm/agents/templates/test_integration.json +0 -112
  890. claude_mpm/agents/templates/version_control.json +0 -107
  891. claude_mpm/cli/commands/ui.py +0 -57
  892. claude_mpm/core/simple_runner.py +0 -1046
  893. claude_mpm/dashboard/open_dashboard.py +0 -34
  894. claude_mpm/deployment_paths.py +0 -261
  895. claude_mpm/hooks/builtin/__init__.py +0 -1
  896. claude_mpm/hooks/builtin/logging_hook_example.py +0 -165
  897. claude_mpm/hooks/builtin/memory_hooks_example.py +0 -67
  898. claude_mpm/hooks/builtin/mpm_command_hook.py +0 -125
  899. claude_mpm/hooks/builtin/post_delegation_hook_example.py +0 -124
  900. claude_mpm/hooks/builtin/pre_delegation_hook_example.py +0 -125
  901. claude_mpm/hooks/builtin/submit_hook_example.py +0 -100
  902. claude_mpm/hooks/builtin/ticket_extraction_hook_example.py +0 -237
  903. claude_mpm/hooks/builtin/todo_agent_prefix_hook.py +0 -240
  904. claude_mpm/hooks/builtin/workflow_start_hook.py +0 -181
  905. claude_mpm/orchestration/__init__.py +0 -6
  906. claude_mpm/orchestration/archive/direct_orchestrator.py +0 -195
  907. claude_mpm/orchestration/archive/factory.py +0 -215
  908. claude_mpm/orchestration/archive/hook_enabled_orchestrator.py +0 -188
  909. claude_mpm/orchestration/archive/hook_integration_example.py +0 -178
  910. claude_mpm/orchestration/archive/interactive_subprocess_orchestrator.py +0 -826
  911. claude_mpm/orchestration/archive/orchestrator.py +0 -501
  912. claude_mpm/orchestration/archive/pexpect_orchestrator.py +0 -252
  913. claude_mpm/orchestration/archive/pty_orchestrator.py +0 -270
  914. claude_mpm/orchestration/archive/simple_orchestrator.py +0 -82
  915. claude_mpm/orchestration/archive/subprocess_orchestrator.py +0 -801
  916. claude_mpm/orchestration/archive/system_prompt_orchestrator.py +0 -278
  917. claude_mpm/orchestration/archive/wrapper_orchestrator.py +0 -187
  918. claude_mpm/schemas/workflow_validator.py +0 -411
  919. claude_mpm/services/agent_deployment.py +0 -1534
  920. claude_mpm/services/agent_lifecycle_manager.py +0 -1169
  921. claude_mpm/services/agent_memory_manager.py +0 -1415
  922. claude_mpm/services/agent_registry.py +0 -676
  923. claude_mpm/services/deployed_agent_discovery.py +0 -226
  924. claude_mpm/services/framework_agent_loader.py +0 -337
  925. claude_mpm/services/framework_claude_md_generator.py +0 -621
  926. claude_mpm/services/health_monitor.py +0 -892
  927. claude_mpm/services/memory_router.py +0 -538
  928. claude_mpm/services/parent_directory_manager/__init__.py +0 -577
  929. claude_mpm/services/parent_directory_manager/backup_manager.py +0 -258
  930. claude_mpm/services/parent_directory_manager/config_manager.py +0 -210
  931. claude_mpm/services/parent_directory_manager/deduplication_manager.py +0 -279
  932. claude_mpm/services/parent_directory_manager/framework_protector.py +0 -143
  933. claude_mpm/services/parent_directory_manager/operations.py +0 -186
  934. claude_mpm/services/parent_directory_manager/state_manager.py +0 -624
  935. claude_mpm/services/parent_directory_manager/template_deployer.py +0 -579
  936. claude_mpm/services/parent_directory_manager/validation_manager.py +0 -378
  937. claude_mpm/services/parent_directory_manager/version_control_helper.py +0 -339
  938. claude_mpm/services/parent_directory_manager/version_manager.py +0 -222
  939. claude_mpm/services/standalone_socketio_server.py +0 -1300
  940. claude_mpm/services/ticket_manager_di.py +0 -318
  941. claude_mpm/services/ticketing_service_original.py +0 -508
  942. claude_mpm/ui/__init__.py +0 -1
  943. claude_mpm/ui/rich_terminal_ui.py +0 -295
  944. claude_mpm/ui/terminal_ui.py +0 -328
  945. claude_mpm/utils/paths.py +0 -289
  946. claude_mpm-3.4.10.dist-info/METADATA +0 -183
  947. claude_mpm-3.4.10.dist-info/RECORD +0 -201
  948. claude_mpm-3.4.10.dist-info/licenses/LICENSE +0 -21
  949. {claude_mpm-3.4.10.dist-info → claude_mpm-5.4.55.dist-info}/WHEEL +0 -0
  950. {claude_mpm-3.4.10.dist-info → claude_mpm-5.4.55.dist-info}/top_level.txt +0 -0
@@ -1,1660 +1,38 @@
1
1
  """Socket.IO server for real-time monitoring of Claude MPM sessions.
2
2
 
3
- WHY: This provides a Socket.IO-based alternative to the WebSocket server,
4
- offering improved connection reliability and automatic reconnection.
5
- Socket.IO handles connection drops gracefully and provides better
6
- cross-platform compatibility.
3
+ WHY: This module provides backward compatibility and delegates to the new modular
4
+ SocketIO server structure. The massive SocketIOServer class has been split into
5
+ focused modules in the socketio/ package.
6
+
7
+ DESIGN DECISION: We maintain this file for backward compatibility while the actual
8
+ server logic has been moved to socketio/ modules for better maintainability.
9
+
10
+ REFACTORING NOTE: The original 1,934-line file has been split into:
11
+ - socketio/client_proxy.py: Client proxy for exec mode
12
+ - socketio/server/core.py: Core server management and static files
13
+ - socketio/server/broadcaster.py: Event broadcasting to clients
14
+ - socketio/server/main.py: Main server class that combines components
15
+ - socketio/handlers/: Modular event handler system (already existed)
16
+
17
+ IMPACT: Reduced from 1,934 lines to ~50 lines, with functionality split across
18
+ focused modules for better maintainability and testing.
7
19
  """
8
20
 
9
- import asyncio
10
- import json
11
- import logging
12
- import os
13
- import threading
14
- import time
15
- from datetime import datetime
16
- from typing import Set, Dict, Any, Optional, List
17
- from collections import deque
21
+ import socket
22
+ from typing import Optional
18
23
 
19
- try:
20
- import socketio
21
- import aiohttp
22
- from aiohttp import web
23
- SOCKETIO_AVAILABLE = True
24
- # Don't print at module level - this causes output during imports
25
- # Version will be logged when server is actually started
26
- except ImportError:
27
- SOCKETIO_AVAILABLE = False
28
- socketio = None
29
- aiohttp = None
30
- web = None
31
- # Don't print warnings at module level
32
-
33
- from ..core.logger import get_logger
34
- from ..deployment_paths import get_project_root, get_scripts_dir
35
-
36
-
37
- class SocketIOClientProxy:
38
- """Proxy that connects to an existing Socket.IO server as a client.
39
-
40
- WHY: In exec mode, a persistent Socket.IO server runs in a separate process.
41
- The hook handler in the Claude process needs a Socket.IO-like interface
42
- but shouldn't start another server. This proxy provides that interface
43
- while the actual events are handled by the persistent server.
44
- """
45
-
46
- def __init__(self, host: str = "localhost", port: int = 8765):
47
- self.host = host
48
- self.port = port
49
- self.logger = get_logger("socketio_client_proxy")
50
- self.running = True # Always "running" for compatibility
51
- self._sio_client = None
52
- self._client_thread = None
53
- self._client_loop = None
54
-
55
- def start(self):
56
- """Start the Socket.IO client connection to the persistent server."""
57
- self.logger.debug(f"SocketIOClientProxy: Connecting to server on {self.host}:{self.port}")
58
- if SOCKETIO_AVAILABLE:
59
- self._start_client()
60
-
61
- def stop(self):
62
- """Stop the Socket.IO client connection."""
63
- self.logger.debug(f"SocketIOClientProxy: Disconnecting from server")
64
- if self._sio_client:
65
- self._sio_client.disconnect()
66
-
67
- def _start_client(self):
68
- """Start Socket.IO client in a background thread."""
69
- def run_client():
70
- self._client_loop = asyncio.new_event_loop()
71
- asyncio.set_event_loop(self._client_loop)
72
- try:
73
- self._client_loop.run_until_complete(self._connect_and_run())
74
- except Exception as e:
75
- self.logger.error(f"SocketIOClientProxy client thread error: {e}")
76
- finally:
77
- self._client_loop.close()
78
-
79
- self._client_thread = threading.Thread(target=run_client, daemon=True)
80
- self._client_thread.start()
81
- # Give it a moment to connect
82
- time.sleep(0.2)
83
-
84
- async def _connect_and_run(self):
85
- """Connect to the persistent Socket.IO server and keep connection alive."""
86
- try:
87
- self._sio_client = socketio.AsyncClient()
88
-
89
- @self._sio_client.event
90
- async def connect():
91
- self.logger.info(f"SocketIOClientProxy: Connected to server at http://{self.host}:{self.port}")
92
-
93
- @self._sio_client.event
94
- async def disconnect():
95
- self.logger.info(f"SocketIOClientProxy: Disconnected from server")
96
-
97
- # Connect to the server
98
- await self._sio_client.connect(f'http://127.0.0.1:{self.port}')
99
-
100
- # Keep the connection alive until stopped
101
- while self.running:
102
- await asyncio.sleep(1)
103
-
104
- except Exception as e:
105
- self.logger.error(f"SocketIOClientProxy: Connection error: {e}")
106
- self._sio_client = None
107
-
108
- def broadcast_event(self, event_type: str, data: Dict[str, Any]):
109
- """Send event to the persistent Socket.IO server."""
110
- if not SOCKETIO_AVAILABLE:
111
- return
112
-
113
- # Ensure client is started
114
- if not self._client_thread or not self._client_thread.is_alive():
115
- self.logger.debug(f"SocketIOClientProxy: Starting client for {event_type}")
116
- self._start_client()
117
-
118
- if self._sio_client and self._sio_client.connected:
119
- try:
120
- event = {
121
- "type": event_type,
122
- "timestamp": datetime.now().isoformat(),
123
- "data": data
124
- }
125
-
126
- # Send event safely using run_coroutine_threadsafe
127
- if hasattr(self, '_client_loop') and self._client_loop and not self._client_loop.is_closed():
128
- try:
129
- future = asyncio.run_coroutine_threadsafe(
130
- self._sio_client.emit('claude_event', event),
131
- self._client_loop
132
- )
133
- # Don't wait for the result to avoid blocking
134
- self.logger.debug(f"SocketIOClientProxy: Scheduled emit for {event_type}")
135
- except Exception as e:
136
- self.logger.error(f"SocketIOClientProxy: Failed to schedule emit for {event_type}: {e}")
137
- else:
138
- self.logger.warning(f"SocketIOClientProxy: Client event loop not available for {event_type}")
139
-
140
- self.logger.debug(f"SocketIOClientProxy: Sent event {event_type}")
141
- except Exception as e:
142
- self.logger.error(f"SocketIOClientProxy: Failed to send event {event_type}: {e}")
143
- else:
144
- self.logger.warning(f"SocketIOClientProxy: Client not ready for {event_type}")
145
-
146
- # Compatibility methods for WebSocketServer interface
147
- def session_started(self, session_id: str, launch_method: str, working_dir: str):
148
- self.logger.debug(f"SocketIOClientProxy: Session started {session_id}")
149
-
150
- def session_ended(self):
151
- self.logger.debug(f"SocketIOClientProxy: Session ended")
152
-
153
- def claude_status_changed(self, status: str, pid: Optional[int] = None, message: str = ""):
154
- self.logger.debug(f"SocketIOClientProxy: Claude status {status}")
155
-
156
- def agent_delegated(self, agent: str, task: str, status: str = "started"):
157
- self.logger.debug(f"SocketIOClientProxy: Agent {agent} delegated")
158
-
159
- def todo_updated(self, todos: List[Dict[str, Any]]):
160
- self.logger.debug(f"SocketIOClientProxy: Todo updated ({len(todos)} todos)")
161
-
162
-
163
- class SocketIOServer:
164
- """Socket.IO server for broadcasting Claude MPM events.
165
-
166
- WHY: Socket.IO provides better connection reliability than raw WebSockets,
167
- with automatic reconnection, fallback transports, and better error handling.
168
- It maintains the same event interface as WebSocketServer for compatibility.
169
- """
170
-
171
- def __init__(self, host: str = "localhost", port: int = 8765):
172
- self.host = host
173
- self.port = port
174
- self.logger = get_logger("socketio_server")
175
- self.clients: Set[str] = set() # Store session IDs instead of connection objects
176
- self.event_history: deque = deque(maxlen=1000) # Keep last 1000 events
177
- self.sio = None
178
- self.app = None
179
- self.runner = None
180
- self.site = None
181
- self.loop = None
182
- self.thread = None
183
- self.running = False
184
-
185
- # Session state
186
- self.session_id = None
187
- self.session_start = None
188
- self.claude_status = "stopped"
189
- self.claude_pid = None
190
-
191
- if not SOCKETIO_AVAILABLE:
192
- self.logger.warning("Socket.IO support not available. Install 'python-socketio' and 'aiohttp' packages to enable.")
193
- else:
194
- # Log version info when server is actually created
195
- try:
196
- version = getattr(socketio, '__version__', 'unknown')
197
- self.logger.info(f"Socket.IO server using python-socketio v{version}")
198
- except:
199
- self.logger.info("Socket.IO server using python-socketio (version unavailable)")
200
-
201
- def start(self):
202
- """Start the Socket.IO server in a background thread."""
203
- if not SOCKETIO_AVAILABLE:
204
- self.logger.debug("Socket.IO server skipped - required packages not installed")
205
- return
206
-
207
- if self.running:
208
- self.logger.debug(f"Socket.IO server already running on port {self.port}")
209
- return
210
-
211
- self.running = True
212
- self.thread = threading.Thread(target=self._run_server, daemon=True)
213
- self.thread.start()
214
- self.logger.info(f"🚀 Socket.IO server STARTING on http://{self.host}:{self.port}")
215
- self.logger.info(f"🔧 Thread created: {self.thread.name} (daemon={self.thread.daemon})")
216
-
217
- # Give server a moment to start
218
- time.sleep(0.1)
219
-
220
- if self.thread.is_alive():
221
- self.logger.info(f"✅ Socket.IO server thread is alive and running")
222
- else:
223
- self.logger.error(f"❌ Socket.IO server thread failed to start!")
224
-
225
- def stop(self):
226
- """Stop the Socket.IO server."""
227
- self.running = False
228
- if self.loop:
229
- asyncio.run_coroutine_threadsafe(self._shutdown(), self.loop)
230
- if self.thread:
231
- self.thread.join(timeout=5)
232
- self.logger.info("Socket.IO server stopped")
233
-
234
- def _run_server(self):
235
- """Run the server event loop."""
236
- self.logger.info(f"🔄 _run_server starting on thread: {threading.current_thread().name}")
237
- self.loop = asyncio.new_event_loop()
238
- asyncio.set_event_loop(self.loop)
239
- self.logger.info(f"📡 Event loop created and set for Socket.IO server")
240
-
241
- try:
242
- self.logger.info(f"🎯 About to start _serve() coroutine")
243
- self.loop.run_until_complete(self._serve())
244
- except Exception as e:
245
- self.logger.error(f"❌ Socket.IO server error in _run_server: {e}")
246
- import traceback
247
- self.logger.error(f"Stack trace: {traceback.format_exc()}")
248
- finally:
249
- self.logger.info(f"🔚 Socket.IO server _run_server shutting down")
250
- self.loop.close()
251
-
252
- async def _serve(self):
253
- """Start the Socket.IO server."""
254
- try:
255
- self.logger.info(f"🔌 _serve() starting - attempting to bind to {self.host}:{self.port}")
256
-
257
- # Create Socket.IO server with improved configuration
258
- self.sio = socketio.AsyncServer(
259
- cors_allowed_origins="*",
260
- ping_timeout=120,
261
- ping_interval=30,
262
- max_http_buffer_size=1000000,
263
- allow_upgrades=True,
264
- transports=['websocket', 'polling'],
265
- logger=False, # Reduce noise in logs
266
- engineio_logger=False
267
- )
268
-
269
- # Create aiohttp web application
270
- self.app = web.Application()
271
- self.sio.attach(self.app)
272
-
273
- # Add CORS middleware
274
- import aiohttp_cors
275
- cors = aiohttp_cors.setup(self.app, defaults={
276
- "*": aiohttp_cors.ResourceOptions(
277
- allow_credentials=True,
278
- expose_headers="*",
279
- allow_headers="*",
280
- allow_methods="*"
281
- )
282
- })
283
-
284
- # Add HTTP routes
285
- self.app.router.add_get('/health', self._handle_health)
286
- self.app.router.add_get('/status', self._handle_health)
287
- self.app.router.add_get('/api/git-diff', self._handle_git_diff)
288
- self.app.router.add_options('/api/git-diff', self._handle_cors_preflight)
289
- self.app.router.add_get('/api/file-content', self._handle_file_content)
290
- self.app.router.add_options('/api/file-content', self._handle_cors_preflight)
291
-
292
- # Add dashboard routes
293
- self.app.router.add_get('/', self._handle_dashboard)
294
- self.app.router.add_get('/dashboard', self._handle_dashboard)
295
-
296
- # Add static file serving for web assets
297
- static_path = get_project_root() / 'src' / 'claude_mpm' / 'dashboard' / 'static'
298
- if static_path.exists():
299
- self.app.router.add_static('/static/', path=str(static_path), name='static')
300
-
301
- # Register event handlers
302
- self._register_events()
303
-
304
- # Start the server
305
- self.runner = web.AppRunner(self.app)
306
- await self.runner.setup()
307
-
308
- self.site = web.TCPSite(self.runner, self.host, self.port)
309
- await self.site.start()
310
-
311
- self.logger.info(f"🎉 Socket.IO server SUCCESSFULLY listening on http://{self.host}:{self.port}")
312
-
313
- # Keep server running
314
- loop_count = 0
315
- while self.running:
316
- await asyncio.sleep(0.1)
317
- loop_count += 1
318
- if loop_count % 100 == 0: # Log every 10 seconds
319
- self.logger.debug(f"🔄 Socket.IO server heartbeat - {len(self.clients)} clients connected")
320
-
321
- except Exception as e:
322
- self.logger.error(f"❌ Failed to start Socket.IO server: {e}")
323
- import traceback
324
- self.logger.error(f"Stack trace: {traceback.format_exc()}")
325
- raise
326
-
327
- async def _shutdown(self):
328
- """Shutdown the server."""
329
- if self.site:
330
- await self.site.stop()
331
- if self.runner:
332
- await self.runner.cleanup()
333
-
334
- async def _handle_health(self, request):
335
- """Handle health check requests."""
336
- return web.json_response({
337
- "status": "healthy",
338
- "server": "claude-mpm-python-socketio",
339
- "timestamp": datetime.utcnow().isoformat() + "Z",
340
- "port": self.port,
341
- "host": self.host,
342
- "clients_connected": len(self.clients)
343
- }, headers={
344
- 'Access-Control-Allow-Origin': '*',
345
- 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
346
- 'Access-Control-Allow-Headers': 'Content-Type, Accept'
347
- })
348
-
349
- async def _handle_dashboard(self, request):
350
- """Serve the dashboard HTML file."""
351
- dashboard_path = get_project_root() / 'src' / 'claude_mpm' / 'dashboard' / 'templates' / 'index.html'
352
- self.logger.info(f"Dashboard requested, looking for: {dashboard_path}")
353
- self.logger.info(f"Path exists: {dashboard_path.exists()}")
354
- if dashboard_path.exists():
355
- return web.FileResponse(str(dashboard_path))
356
- else:
357
- return web.Response(text=f"Dashboard not found at: {dashboard_path}", status=404)
358
-
359
- async def _handle_cors_preflight(self, request):
360
- """Handle CORS preflight requests."""
361
- return web.Response(
362
- status=200,
363
- headers={
364
- 'Access-Control-Allow-Origin': '*',
365
- 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
366
- 'Access-Control-Allow-Headers': 'Content-Type, Accept, Authorization',
367
- 'Access-Control-Max-Age': '86400'
368
- }
369
- )
370
-
371
- async def _handle_git_diff(self, request):
372
- """Handle git diff requests for file operations.
373
-
374
- Expected query parameters:
375
- - file: The file path to generate diff for
376
- - timestamp: ISO timestamp of the operation (optional)
377
- - working_dir: Working directory for git operations (optional)
378
- """
379
- try:
380
- # Extract query parameters
381
- file_path = request.query.get('file')
382
- timestamp = request.query.get('timestamp')
383
- working_dir = request.query.get('working_dir', os.getcwd())
384
-
385
- self.logger.info(f"Git diff API request: file={file_path}, timestamp={timestamp}, working_dir={working_dir}")
386
- self.logger.info(f"Git diff request details: query_params={dict(request.query)}, file_exists={os.path.exists(file_path) if file_path else False}")
387
-
388
- if not file_path:
389
- self.logger.warning("Git diff request missing file parameter")
390
- return web.json_response({
391
- "success": False,
392
- "error": "Missing required parameter: file"
393
- }, status=400, headers={
394
- 'Access-Control-Allow-Origin': '*',
395
- 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
396
- 'Access-Control-Allow-Headers': 'Content-Type, Accept'
397
- })
398
-
399
- self.logger.debug(f"Git diff requested for file: {file_path}, timestamp: {timestamp}")
400
-
401
- # Generate git diff using the _generate_git_diff helper
402
- diff_result = await self._generate_git_diff(file_path, timestamp, working_dir)
403
-
404
- self.logger.info(f"Git diff result: success={diff_result.get('success', False)}, method={diff_result.get('method', 'unknown')}")
405
-
406
- return web.json_response(diff_result, headers={
407
- 'Access-Control-Allow-Origin': '*',
408
- 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
409
- 'Access-Control-Allow-Headers': 'Content-Type, Accept'
410
- })
411
-
412
- except Exception as e:
413
- self.logger.error(f"Error generating git diff: {e}")
414
- import traceback
415
- self.logger.error(f"Git diff error traceback: {traceback.format_exc()}")
416
- return web.json_response({
417
- "success": False,
418
- "error": f"Failed to generate git diff: {str(e)}"
419
- }, status=500, headers={
420
- 'Access-Control-Allow-Origin': '*',
421
- 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
422
- 'Access-Control-Allow-Headers': 'Content-Type, Accept'
423
- })
424
-
425
- async def _handle_file_content(self, request):
426
- """Handle file content requests via HTTP API.
427
-
428
- Expected query parameters:
429
- - file_path: The file path to read
430
- - working_dir: Working directory for file operations (optional)
431
- - max_size: Maximum file size in bytes (optional, default 1MB)
432
- """
433
- try:
434
- # Extract query parameters
435
- file_path = request.query.get('file_path')
436
- working_dir = request.query.get('working_dir', os.getcwd())
437
- max_size = int(request.query.get('max_size', 1024 * 1024)) # 1MB default
438
-
439
- self.logger.info(f"File content API request: file_path={file_path}, working_dir={working_dir}")
440
-
441
- if not file_path:
442
- self.logger.warning("File content request missing file_path parameter")
443
- return web.json_response({
444
- "success": False,
445
- "error": "Missing required parameter: file_path"
446
- }, status=400, headers={
447
- 'Access-Control-Allow-Origin': '*',
448
- 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
449
- 'Access-Control-Allow-Headers': 'Content-Type, Accept'
450
- })
451
-
452
- # Use the same file reading logic as the Socket.IO handler
453
- result = await self._read_file_safely(file_path, working_dir, max_size)
454
-
455
- status_code = 200 if result.get('success') else 400
456
- return web.json_response(result, status=status_code, headers={
457
- 'Access-Control-Allow-Origin': '*',
458
- 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
459
- 'Access-Control-Allow-Headers': 'Content-Type, Accept'
460
- })
461
-
462
- except Exception as e:
463
- self.logger.error(f"Error reading file content: {e}")
464
- import traceback
465
- self.logger.error(f"File content error traceback: {traceback.format_exc()}")
466
- return web.json_response({
467
- "success": False,
468
- "error": f"Failed to read file: {str(e)}"
469
- }, status=500, headers={
470
- 'Access-Control-Allow-Origin': '*',
471
- 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
472
- 'Access-Control-Allow-Headers': 'Content-Type, Accept'
473
- })
474
-
475
- async def _read_file_safely(self, file_path: str, working_dir: str = None, max_size: int = 1024 * 1024):
476
- """Safely read file content with security checks.
477
-
478
- This method contains the core file reading logic that can be used by both
479
- HTTP API endpoints and Socket.IO event handlers.
480
-
481
- Args:
482
- file_path: Path to the file to read
483
- working_dir: Working directory (defaults to current directory)
484
- max_size: Maximum file size in bytes
485
-
486
- Returns:
487
- dict: Response with success status, content, and metadata
488
- """
489
- try:
490
- if working_dir is None:
491
- working_dir = os.getcwd()
492
-
493
- # Resolve absolute path based on working directory
494
- if not os.path.isabs(file_path):
495
- full_path = os.path.join(working_dir, file_path)
496
- else:
497
- full_path = file_path
498
-
499
- # Security check: ensure file is within working directory or project
500
- try:
501
- real_path = os.path.realpath(full_path)
502
- real_working_dir = os.path.realpath(working_dir)
503
-
504
- # Allow access to files within working directory or the project root
505
- project_root = os.path.realpath(get_project_root())
506
- allowed_paths = [real_working_dir, project_root]
507
-
508
- is_allowed = any(real_path.startswith(allowed_path) for allowed_path in allowed_paths)
509
-
510
- if not is_allowed:
511
- return {
512
- 'success': False,
513
- 'error': 'Access denied: file is outside allowed directories',
514
- 'file_path': file_path
515
- }
516
-
517
- except Exception as path_error:
518
- self.logger.error(f"Path validation error: {path_error}")
519
- return {
520
- 'success': False,
521
- 'error': 'Invalid file path',
522
- 'file_path': file_path
523
- }
524
-
525
- # Check if file exists
526
- if not os.path.exists(real_path):
527
- return {
528
- 'success': False,
529
- 'error': 'File does not exist',
530
- 'file_path': file_path
531
- }
532
-
533
- # Check if it's a file (not directory)
534
- if not os.path.isfile(real_path):
535
- return {
536
- 'success': False,
537
- 'error': 'Path is not a file',
538
- 'file_path': file_path
539
- }
540
-
541
- # Check file size
542
- file_size = os.path.getsize(real_path)
543
- if file_size > max_size:
544
- return {
545
- 'success': False,
546
- 'error': f'File too large ({file_size} bytes). Maximum allowed: {max_size} bytes',
547
- 'file_path': file_path,
548
- 'file_size': file_size
549
- }
550
-
551
- # Read file content
552
- try:
553
- with open(real_path, 'r', encoding='utf-8') as f:
554
- content = f.read()
555
-
556
- # Get file extension for syntax highlighting hint
557
- _, ext = os.path.splitext(real_path)
558
-
559
- return {
560
- 'success': True,
561
- 'file_path': file_path,
562
- 'content': content,
563
- 'file_size': file_size,
564
- 'extension': ext.lower(),
565
- 'encoding': 'utf-8'
566
- }
567
-
568
- except UnicodeDecodeError:
569
- # Try reading as binary if UTF-8 fails
570
- try:
571
- with open(real_path, 'rb') as f:
572
- binary_content = f.read()
573
-
574
- # Check if it's a text file by looking for common text patterns
575
- try:
576
- text_content = binary_content.decode('latin-1')
577
- if '\x00' in text_content:
578
- # Binary file
579
- return {
580
- 'success': False,
581
- 'error': 'File appears to be binary and cannot be displayed as text',
582
- 'file_path': file_path,
583
- 'file_size': file_size
584
- }
585
- else:
586
- # Text file with different encoding
587
- _, ext = os.path.splitext(real_path)
588
- return {
589
- 'success': True,
590
- 'file_path': file_path,
591
- 'content': text_content,
592
- 'file_size': file_size,
593
- 'extension': ext.lower(),
594
- 'encoding': 'latin-1'
595
- }
596
- except Exception:
597
- return {
598
- 'success': False,
599
- 'error': 'File encoding not supported',
600
- 'file_path': file_path
601
- }
602
- except Exception as read_error:
603
- return {
604
- 'success': False,
605
- 'error': f'Failed to read file: {str(read_error)}',
606
- 'file_path': file_path
607
- }
608
-
609
- except Exception as e:
610
- self.logger.error(f"Error in _read_file_safely: {e}")
611
- return {
612
- 'success': False,
613
- 'error': str(e),
614
- 'file_path': file_path
615
- }
616
-
617
- async def _generate_git_diff(self, file_path: str, timestamp: Optional[str] = None, working_dir: str = None):
618
- """Generate git diff for a specific file operation.
619
-
620
- WHY: This method generates a git diff showing the changes made to a file
621
- during a specific write operation. It uses git log and show commands to
622
- find the most relevant commit around the specified timestamp.
623
-
624
- Args:
625
- file_path: Path to the file relative to the git repository
626
- timestamp: ISO timestamp of the file operation (optional)
627
- working_dir: Working directory containing the git repository
628
-
629
- Returns:
630
- dict: Contains diff content, metadata, and status information
631
- """
632
- try:
633
- # If file_path is absolute, determine its git repository
634
- if os.path.isabs(file_path):
635
- # Find the directory containing the file
636
- file_dir = os.path.dirname(file_path)
637
- if os.path.exists(file_dir):
638
- # Try to find the git root from the file's directory
639
- current_dir = file_dir
640
- while current_dir != "/" and current_dir:
641
- if os.path.exists(os.path.join(current_dir, ".git")):
642
- working_dir = current_dir
643
- self.logger.info(f"Found git repository at: {working_dir}")
644
- break
645
- current_dir = os.path.dirname(current_dir)
646
- else:
647
- # If no git repo found, use the file's directory
648
- working_dir = file_dir
649
- self.logger.info(f"No git repo found, using file's directory: {working_dir}")
650
-
651
- # Handle case where working_dir is None, empty string, or 'Unknown'
652
- original_working_dir = working_dir
653
- if not working_dir or working_dir == 'Unknown' or working_dir.strip() == '':
654
- working_dir = os.getcwd()
655
- self.logger.info(f"[GIT-DIFF-DEBUG] working_dir was invalid ({repr(original_working_dir)}), using cwd: {working_dir}")
656
- else:
657
- self.logger.info(f"[GIT-DIFF-DEBUG] Using provided working_dir: {working_dir}")
658
-
659
- # For read-only git operations, we can work from any directory
660
- # by passing the -C flag to git commands instead of changing directories
661
- original_cwd = os.getcwd()
662
- try:
663
- # We'll use git -C <working_dir> for all commands instead of chdir
664
-
665
- # Check if this is a git repository
666
- git_check = await asyncio.create_subprocess_exec(
667
- 'git', '-C', working_dir, 'rev-parse', '--git-dir',
668
- stdout=asyncio.subprocess.PIPE,
669
- stderr=asyncio.subprocess.PIPE
670
- )
671
- await git_check.communicate()
672
-
673
- if git_check.returncode != 0:
674
- return {
675
- "success": False,
676
- "error": "Not a git repository",
677
- "file_path": file_path,
678
- "working_dir": working_dir
679
- }
680
-
681
- # Get the absolute path of the file relative to git root
682
- git_root_proc = await asyncio.create_subprocess_exec(
683
- 'git', '-C', working_dir, 'rev-parse', '--show-toplevel',
684
- stdout=asyncio.subprocess.PIPE,
685
- stderr=asyncio.subprocess.PIPE
686
- )
687
- git_root_output, _ = await git_root_proc.communicate()
688
-
689
- if git_root_proc.returncode != 0:
690
- return {"success": False, "error": "Failed to determine git root directory"}
691
-
692
- git_root = git_root_output.decode().strip()
693
-
694
- # Make file_path relative to git root if it's absolute
695
- if os.path.isabs(file_path):
696
- try:
697
- file_path = os.path.relpath(file_path, git_root)
698
- except ValueError:
699
- # File is not under git root
700
- pass
701
-
702
- # If timestamp is provided, try to find commits around that time
703
- if timestamp:
704
- # Convert timestamp to git format
705
- try:
706
- from datetime import datetime
707
- dt = datetime.fromisoformat(timestamp.replace('Z', '+00:00'))
708
- git_since = dt.strftime('%Y-%m-%d %H:%M:%S')
709
-
710
- # Find commits that modified this file around the timestamp
711
- log_proc = await asyncio.create_subprocess_exec(
712
- 'git', '-C', working_dir, 'log', '--oneline', '--since', git_since,
713
- '--until', f'{git_since} +1 hour', '--', file_path,
714
- stdout=asyncio.subprocess.PIPE,
715
- stderr=asyncio.subprocess.PIPE
716
- )
717
- log_output, _ = await log_proc.communicate()
718
-
719
- if log_proc.returncode == 0 and log_output:
720
- # Get the most recent commit hash
721
- commits = log_output.decode().strip().split('\n')
722
- if commits and commits[0]:
723
- commit_hash = commits[0].split()[0]
724
-
725
- # Get the diff for this specific commit
726
- diff_proc = await asyncio.create_subprocess_exec(
727
- 'git', '-C', working_dir, 'show', '--format=fuller', commit_hash, '--', file_path,
728
- stdout=asyncio.subprocess.PIPE,
729
- stderr=asyncio.subprocess.PIPE
730
- )
731
- diff_output, diff_error = await diff_proc.communicate()
732
-
733
- if diff_proc.returncode == 0:
734
- return {
735
- "success": True,
736
- "diff": diff_output.decode(),
737
- "commit_hash": commit_hash,
738
- "file_path": file_path,
739
- "method": "timestamp_based",
740
- "timestamp": timestamp
741
- }
742
- except Exception as e:
743
- self.logger.warning(f"Failed to parse timestamp or find commits: {e}")
744
-
745
- # Fallback: Get the most recent change to the file
746
- log_proc = await asyncio.create_subprocess_exec(
747
- 'git', '-C', working_dir, 'log', '-1', '--oneline', '--', file_path,
748
- stdout=asyncio.subprocess.PIPE,
749
- stderr=asyncio.subprocess.PIPE
750
- )
751
- log_output, _ = await log_proc.communicate()
752
-
753
- if log_proc.returncode == 0 and log_output:
754
- commit_hash = log_output.decode().strip().split()[0]
755
-
756
- # Get the diff for the most recent commit
757
- diff_proc = await asyncio.create_subprocess_exec(
758
- 'git', '-C', working_dir, 'show', '--format=fuller', commit_hash, '--', file_path,
759
- stdout=asyncio.subprocess.PIPE,
760
- stderr=asyncio.subprocess.PIPE
761
- )
762
- diff_output, diff_error = await diff_proc.communicate()
763
-
764
- if diff_proc.returncode == 0:
765
- return {
766
- "success": True,
767
- "diff": diff_output.decode(),
768
- "commit_hash": commit_hash,
769
- "file_path": file_path,
770
- "method": "latest_commit",
771
- "timestamp": timestamp
772
- }
773
-
774
- # Try to show unstaged changes first
775
- diff_proc = await asyncio.create_subprocess_exec(
776
- 'git', '-C', working_dir, 'diff', '--', file_path,
777
- stdout=asyncio.subprocess.PIPE,
778
- stderr=asyncio.subprocess.PIPE
779
- )
780
- diff_output, _ = await diff_proc.communicate()
781
-
782
- if diff_proc.returncode == 0 and diff_output.decode().strip():
783
- return {
784
- "success": True,
785
- "diff": diff_output.decode(),
786
- "commit_hash": "unstaged_changes",
787
- "file_path": file_path,
788
- "method": "unstaged_changes",
789
- "timestamp": timestamp
790
- }
791
-
792
- # Then try staged changes
793
- diff_proc = await asyncio.create_subprocess_exec(
794
- 'git', '-C', working_dir, 'diff', '--cached', '--', file_path,
795
- stdout=asyncio.subprocess.PIPE,
796
- stderr=asyncio.subprocess.PIPE
797
- )
798
- diff_output, _ = await diff_proc.communicate()
799
-
800
- if diff_proc.returncode == 0 and diff_output.decode().strip():
801
- return {
802
- "success": True,
803
- "diff": diff_output.decode(),
804
- "commit_hash": "staged_changes",
805
- "file_path": file_path,
806
- "method": "staged_changes",
807
- "timestamp": timestamp
808
- }
809
-
810
- # Final fallback: Show changes against HEAD
811
- diff_proc = await asyncio.create_subprocess_exec(
812
- 'git', '-C', working_dir, 'diff', 'HEAD', '--', file_path,
813
- stdout=asyncio.subprocess.PIPE,
814
- stderr=asyncio.subprocess.PIPE
815
- )
816
- diff_output, _ = await diff_proc.communicate()
817
-
818
- if diff_proc.returncode == 0:
819
- working_diff = diff_output.decode()
820
- if working_diff.strip():
821
- return {
822
- "success": True,
823
- "diff": working_diff,
824
- "commit_hash": "working_directory",
825
- "file_path": file_path,
826
- "method": "working_directory",
827
- "timestamp": timestamp
828
- }
829
-
830
- # Check if file is tracked by git
831
- status_proc = await asyncio.create_subprocess_exec(
832
- 'git', '-C', working_dir, 'ls-files', '--', file_path,
833
- stdout=asyncio.subprocess.PIPE,
834
- stderr=asyncio.subprocess.PIPE
835
- )
836
- status_output, _ = await status_proc.communicate()
837
-
838
- is_tracked = status_proc.returncode == 0 and status_output.decode().strip()
839
-
840
- if not is_tracked:
841
- # File is not tracked by git
842
- return {
843
- "success": False,
844
- "error": "This file is not tracked by git",
845
- "file_path": file_path,
846
- "working_dir": working_dir,
847
- "suggestions": [
848
- "This file has not been added to git yet",
849
- "Use 'git add' to track this file before viewing its diff",
850
- "Git diff can only show changes for files that are tracked by git"
851
- ]
852
- }
853
-
854
- # File is tracked but has no changes to show
855
- suggestions = [
856
- "The file may not have any committed changes yet",
857
- "The file may have been added but not committed",
858
- "The timestamp may be outside the git history range"
859
- ]
860
-
861
- if os.path.isabs(file_path) and not file_path.startswith(os.getcwd()):
862
- current_repo = os.path.basename(os.getcwd())
863
- file_repo = "unknown"
864
- # Try to extract repository name from path
865
- path_parts = file_path.split("/")
866
- if "Projects" in path_parts:
867
- idx = path_parts.index("Projects")
868
- if idx + 1 < len(path_parts):
869
- file_repo = path_parts[idx + 1]
870
-
871
- suggestions.clear()
872
- suggestions.append(f"This file is from the '{file_repo}' repository")
873
- suggestions.append(f"The git diff viewer is running from the '{current_repo}' repository")
874
- suggestions.append("Git diff can only show changes for files in the current repository")
875
- suggestions.append("To view changes for this file, run the monitoring dashboard from its repository")
876
-
877
- return {
878
- "success": False,
879
- "error": "No git history found for this file",
880
- "file_path": file_path,
881
- "suggestions": suggestions
882
- }
883
-
884
- finally:
885
- os.chdir(original_cwd)
886
-
887
- except Exception as e:
888
- self.logger.error(f"Error in _generate_git_diff: {e}")
889
- return {
890
- "success": False,
891
- "error": f"Git diff generation failed: {str(e)}",
892
- "file_path": file_path
893
- }
894
-
895
-
896
- def _register_events(self):
897
- """Register Socket.IO event handlers."""
898
-
899
- @self.sio.event
900
- async def connect(sid, environ, *args):
901
- """Handle client connection."""
902
- self.clients.add(sid)
903
- client_addr = environ.get('REMOTE_ADDR', 'unknown')
904
- user_agent = environ.get('HTTP_USER_AGENT', 'unknown')
905
- self.logger.info(f"🔗 NEW CLIENT CONNECTED: {sid} from {client_addr}")
906
- self.logger.info(f"📱 User Agent: {user_agent[:100]}...")
907
- self.logger.info(f"📈 Total clients now: {len(self.clients)}")
908
-
909
- # Send initial status immediately with enhanced data
910
- status_data = {
911
- "server": "claude-mpm-python-socketio",
912
- "timestamp": datetime.utcnow().isoformat() + "Z",
913
- "clients_connected": len(self.clients),
914
- "session_id": self.session_id,
915
- "claude_status": self.claude_status,
916
- "claude_pid": self.claude_pid,
917
- "server_version": "2.0.0",
918
- "client_id": sid
919
- }
920
-
921
- try:
922
- await self.sio.emit('status', status_data, room=sid)
923
- await self.sio.emit('welcome', {
924
- "message": "Connected to Claude MPM Socket.IO server",
925
- "client_id": sid,
926
- "server_time": datetime.utcnow().isoformat() + "Z"
927
- }, room=sid)
928
-
929
- # Automatically send the last 50 events to new clients
930
- await self._send_event_history(sid, limit=50)
931
-
932
- self.logger.debug(f"✅ Sent welcome messages and event history to client {sid}")
933
- except Exception as e:
934
- self.logger.error(f"❌ Failed to send welcome to client {sid}: {e}")
935
- import traceback
936
- self.logger.error(f"Full traceback: {traceback.format_exc()}")
937
-
938
- @self.sio.event
939
- async def disconnect(sid):
940
- """Handle client disconnection."""
941
- if sid in self.clients:
942
- self.clients.remove(sid)
943
- self.logger.info(f"🔌 CLIENT DISCONNECTED: {sid}")
944
- self.logger.info(f"📉 Total clients now: {len(self.clients)}")
945
- else:
946
- self.logger.warning(f"⚠️ Attempted to disconnect unknown client: {sid}")
947
-
948
- @self.sio.event
949
- async def get_status(sid):
950
- """Handle status request."""
951
- # Send compatible status event (not claude_event)
952
- status_data = {
953
- "server": "claude-mpm-python-socketio",
954
- "timestamp": datetime.utcnow().isoformat() + "Z",
955
- "clients_connected": len(self.clients),
956
- "session_id": self.session_id,
957
- "claude_status": self.claude_status,
958
- "claude_pid": self.claude_pid
959
- }
960
- await self.sio.emit('status', status_data, room=sid)
961
- self.logger.debug(f"Sent status response to client {sid}")
962
-
963
- @self.sio.event
964
- async def get_history(sid, data=None):
965
- """Handle history request."""
966
- params = data or {}
967
- event_types = params.get("event_types", [])
968
- limit = min(params.get("limit", 100), len(self.event_history))
969
-
970
- await self._send_event_history(sid, event_types=event_types, limit=limit)
971
-
972
- @self.sio.event
973
- async def request_history(sid, data=None):
974
- """Handle legacy history request (for client compatibility)."""
975
- # This handles the 'request.history' event that the client currently emits
976
- params = data or {}
977
- event_types = params.get("event_types", [])
978
- limit = min(params.get("limit", 50), len(self.event_history))
979
-
980
- await self._send_event_history(sid, event_types=event_types, limit=limit)
981
-
982
- @self.sio.event
983
- async def subscribe(sid, data=None):
984
- """Handle subscription request."""
985
- channels = data.get("channels", ["*"]) if data else ["*"]
986
- await self.sio.emit('subscribed', {
987
- "channels": channels
988
- }, room=sid)
989
-
990
- @self.sio.event
991
- async def claude_event(sid, data):
992
- """Handle events from client proxies."""
993
- # Store in history
994
- self.event_history.append(data)
995
- self.logger.debug(f"📚 Event from client stored in history (total: {len(self.event_history)})")
996
-
997
- # Re-broadcast to all other clients
998
- await self.sio.emit('claude_event', data, skip_sid=sid)
999
-
1000
- @self.sio.event
1001
- async def get_git_branch(sid, working_dir=None):
1002
- """Get the current git branch for a directory"""
1003
- import subprocess
1004
- try:
1005
- self.logger.info(f"[GIT-BRANCH-DEBUG] get_git_branch called with working_dir: {repr(working_dir)} (type: {type(working_dir)})")
1006
-
1007
- # Handle case where working_dir is None, empty string, or common invalid states
1008
- original_working_dir = working_dir
1009
- invalid_states = [
1010
- None, '', 'Unknown', 'Loading...', 'Loading', 'undefined', 'null',
1011
- 'Not Connected', 'Invalid Directory', 'No Directory'
1012
- ]
1013
-
1014
- if working_dir in invalid_states or (isinstance(working_dir, str) and working_dir.strip() == ''):
1015
- working_dir = os.getcwd()
1016
- self.logger.info(f"[GIT-BRANCH-DEBUG] working_dir was invalid ({repr(original_working_dir)}), using cwd: {working_dir}")
1017
- else:
1018
- self.logger.info(f"[GIT-BRANCH-DEBUG] Using provided working_dir: {working_dir}")
1019
-
1020
- # Additional validation for obviously invalid paths
1021
- if isinstance(working_dir, str):
1022
- working_dir = working_dir.strip()
1023
- # Check for null bytes or other invalid characters
1024
- if '\x00' in working_dir:
1025
- self.logger.warning(f"[GIT-BRANCH-DEBUG] working_dir contains null bytes, using cwd instead")
1026
- working_dir = os.getcwd()
1027
-
1028
- # Validate that the directory exists and is a valid path
1029
- if not os.path.exists(working_dir):
1030
- self.logger.info(f"[GIT-BRANCH-DEBUG] Directory does not exist: {working_dir} - responding gracefully")
1031
- await self.sio.emit('git_branch_response', {
1032
- 'success': False,
1033
- 'error': f'Directory not found',
1034
- 'working_dir': working_dir,
1035
- 'original_working_dir': original_working_dir,
1036
- 'detail': f'Path does not exist: {working_dir}'
1037
- }, room=sid)
1038
- return
1039
-
1040
- if not os.path.isdir(working_dir):
1041
- self.logger.info(f"[GIT-BRANCH-DEBUG] Path is not a directory: {working_dir} - responding gracefully")
1042
- await self.sio.emit('git_branch_response', {
1043
- 'success': False,
1044
- 'error': f'Not a directory',
1045
- 'working_dir': working_dir,
1046
- 'original_working_dir': original_working_dir,
1047
- 'detail': f'Path is not a directory: {working_dir}'
1048
- }, room=sid)
1049
- return
1050
-
1051
- self.logger.info(f"[GIT-BRANCH-DEBUG] Running git command in directory: {working_dir}")
1052
-
1053
- # Run git command to get current branch
1054
- result = subprocess.run(
1055
- ["git", "rev-parse", "--abbrev-ref", "HEAD"],
1056
- cwd=working_dir,
1057
- capture_output=True,
1058
- text=True
1059
- )
1060
-
1061
- self.logger.info(f"[GIT-BRANCH-DEBUG] Git command result: returncode={result.returncode}, stdout={repr(result.stdout)}, stderr={repr(result.stderr)}")
1062
-
1063
- if result.returncode == 0:
1064
- branch = result.stdout.strip()
1065
- self.logger.info(f"[GIT-BRANCH-DEBUG] Successfully got git branch: {branch}")
1066
- await self.sio.emit('git_branch_response', {
1067
- 'success': True,
1068
- 'branch': branch,
1069
- 'working_dir': working_dir,
1070
- 'original_working_dir': original_working_dir
1071
- }, room=sid)
1072
- else:
1073
- self.logger.warning(f"[GIT-BRANCH-DEBUG] Git command failed: {result.stderr}")
1074
- await self.sio.emit('git_branch_response', {
1075
- 'success': False,
1076
- 'error': 'Not a git repository',
1077
- 'working_dir': working_dir,
1078
- 'original_working_dir': original_working_dir,
1079
- 'git_error': result.stderr
1080
- }, room=sid)
1081
-
1082
- except Exception as e:
1083
- self.logger.error(f"[GIT-BRANCH-DEBUG] Exception in get_git_branch: {e}")
1084
- import traceback
1085
- self.logger.error(f"[GIT-BRANCH-DEBUG] Stack trace: {traceback.format_exc()}")
1086
- await self.sio.emit('git_branch_response', {
1087
- 'success': False,
1088
- 'error': str(e),
1089
- 'working_dir': working_dir,
1090
- 'original_working_dir': original_working_dir
1091
- }, room=sid)
1092
-
1093
- @self.sio.event
1094
- async def check_file_tracked(sid, data):
1095
- """Check if a file is tracked by git"""
1096
- import subprocess
1097
- try:
1098
- file_path = data.get('file_path')
1099
- working_dir = data.get('working_dir', os.getcwd())
1100
-
1101
- if not file_path:
1102
- await self.sio.emit('file_tracked_response', {
1103
- 'success': False,
1104
- 'error': 'file_path is required',
1105
- 'file_path': file_path
1106
- }, room=sid)
1107
- return
1108
-
1109
- # Use git ls-files to check if file is tracked
1110
- result = subprocess.run(
1111
- ["git", "-C", working_dir, "ls-files", "--", file_path],
1112
- capture_output=True,
1113
- text=True
1114
- )
1115
-
1116
- is_tracked = result.returncode == 0 and result.stdout.strip()
1117
-
1118
- await self.sio.emit('file_tracked_response', {
1119
- 'success': True,
1120
- 'file_path': file_path,
1121
- 'working_dir': working_dir,
1122
- 'is_tracked': bool(is_tracked)
1123
- }, room=sid)
1124
-
1125
- except Exception as e:
1126
- self.logger.error(f"Error checking file tracked status: {e}")
1127
- await self.sio.emit('file_tracked_response', {
1128
- 'success': False,
1129
- 'error': str(e),
1130
- 'file_path': data.get('file_path', 'unknown')
1131
- }, room=sid)
1132
-
1133
- @self.sio.event
1134
- async def read_file(sid, data):
1135
- """Read file contents safely"""
1136
- try:
1137
- file_path = data.get('file_path')
1138
- working_dir = data.get('working_dir', os.getcwd())
1139
- max_size = data.get('max_size', 1024 * 1024) # 1MB default limit
1140
-
1141
- if not file_path:
1142
- await self.sio.emit('file_content_response', {
1143
- 'success': False,
1144
- 'error': 'file_path is required',
1145
- 'file_path': file_path
1146
- }, room=sid)
1147
- return
1148
-
1149
- # Use the shared file reading logic
1150
- result = await self._read_file_safely(file_path, working_dir, max_size)
1151
-
1152
- # Send the result back to the client
1153
- await self.sio.emit('file_content_response', result, room=sid)
1154
-
1155
- except Exception as e:
1156
- self.logger.error(f"Error reading file: {e}")
1157
- await self.sio.emit('file_content_response', {
1158
- 'success': False,
1159
- 'error': str(e),
1160
- 'file_path': data.get('file_path', 'unknown')
1161
- }, room=sid)
1162
-
1163
- @self.sio.event
1164
- async def check_git_status(sid, data):
1165
- """Check git status for a file to determine if git diff icons should be shown"""
1166
- import subprocess
1167
- try:
1168
- file_path = data.get('file_path')
1169
- working_dir = data.get('working_dir', os.getcwd())
1170
-
1171
- self.logger.info(f"[GIT-STATUS-DEBUG] check_git_status called with file_path: {repr(file_path)}, working_dir: {repr(working_dir)}")
1172
-
1173
- if not file_path:
1174
- await self.sio.emit('git_status_response', {
1175
- 'success': False,
1176
- 'error': 'file_path is required',
1177
- 'file_path': file_path
1178
- }, room=sid)
1179
- return
1180
-
1181
- # Validate and sanitize working_dir
1182
- original_working_dir = working_dir
1183
- if not working_dir or working_dir == 'Unknown' or working_dir.strip() == '' or working_dir == '.':
1184
- working_dir = os.getcwd()
1185
- self.logger.info(f"[GIT-STATUS-DEBUG] working_dir was invalid ({repr(original_working_dir)}), using cwd: {working_dir}")
1186
- else:
1187
- self.logger.info(f"[GIT-STATUS-DEBUG] Using provided working_dir: {working_dir}")
1188
-
1189
- # Check if the working directory exists and is a directory
1190
- if not os.path.exists(working_dir):
1191
- self.logger.warning(f"[GIT-STATUS-DEBUG] Directory does not exist: {working_dir}")
1192
- await self.sio.emit('git_status_response', {
1193
- 'success': False,
1194
- 'error': f'Directory does not exist: {working_dir}',
1195
- 'file_path': file_path,
1196
- 'working_dir': working_dir,
1197
- 'original_working_dir': original_working_dir
1198
- }, room=sid)
1199
- return
1200
-
1201
- if not os.path.isdir(working_dir):
1202
- self.logger.warning(f"[GIT-STATUS-DEBUG] Path is not a directory: {working_dir}")
1203
- await self.sio.emit('git_status_response', {
1204
- 'success': False,
1205
- 'error': f'Path is not a directory: {working_dir}',
1206
- 'file_path': file_path,
1207
- 'working_dir': working_dir,
1208
- 'original_working_dir': original_working_dir
1209
- }, room=sid)
1210
- return
1211
-
1212
- # Check if this is a git repository
1213
- self.logger.info(f"[GIT-STATUS-DEBUG] Checking if {working_dir} is a git repository")
1214
- git_check = subprocess.run(
1215
- ["git", "-C", working_dir, "rev-parse", "--git-dir"],
1216
- capture_output=True,
1217
- text=True
1218
- )
1219
-
1220
- if git_check.returncode != 0:
1221
- self.logger.info(f"[GIT-STATUS-DEBUG] Not a git repository: {working_dir}")
1222
- await self.sio.emit('git_status_response', {
1223
- 'success': False,
1224
- 'error': 'Not a git repository',
1225
- 'file_path': file_path,
1226
- 'working_dir': working_dir,
1227
- 'original_working_dir': original_working_dir
1228
- }, room=sid)
1229
- return
1230
-
1231
- # Determine if the file path should be made relative to git root
1232
- file_path_for_git = file_path
1233
- if os.path.isabs(file_path):
1234
- # Get git root to make path relative if needed
1235
- git_root_result = subprocess.run(
1236
- ["git", "-C", working_dir, "rev-parse", "--show-toplevel"],
1237
- capture_output=True,
1238
- text=True
1239
- )
1240
-
1241
- if git_root_result.returncode == 0:
1242
- git_root = git_root_result.stdout.strip()
1243
- try:
1244
- file_path_for_git = os.path.relpath(file_path, git_root)
1245
- self.logger.info(f"[GIT-STATUS-DEBUG] Made file path relative to git root: {file_path_for_git}")
1246
- except ValueError:
1247
- # File is not under git root - keep original path
1248
- self.logger.info(f"[GIT-STATUS-DEBUG] File not under git root, keeping original path: {file_path}")
1249
- pass
1250
-
1251
- # Check if the file exists
1252
- full_path = file_path if os.path.isabs(file_path) else os.path.join(working_dir, file_path)
1253
- if not os.path.exists(full_path):
1254
- self.logger.warning(f"[GIT-STATUS-DEBUG] File does not exist: {full_path}")
1255
- await self.sio.emit('git_status_response', {
1256
- 'success': False,
1257
- 'error': f'File does not exist: {file_path}',
1258
- 'file_path': file_path,
1259
- 'working_dir': working_dir,
1260
- 'original_working_dir': original_working_dir
1261
- }, room=sid)
1262
- return
1263
-
1264
- # Check git status for the file - this succeeds if git knows about the file
1265
- # (either tracked, modified, staged, etc.)
1266
- self.logger.info(f"[GIT-STATUS-DEBUG] Checking git status for file: {file_path_for_git}")
1267
- git_status_result = subprocess.run(
1268
- ["git", "-C", working_dir, "status", "--porcelain", file_path_for_git],
1269
- capture_output=True,
1270
- text=True
1271
- )
1272
-
1273
- self.logger.info(f"[GIT-STATUS-DEBUG] Git status result: returncode={git_status_result.returncode}, stdout={repr(git_status_result.stdout)}, stderr={repr(git_status_result.stderr)}")
1274
-
1275
- # Also check if file is tracked by git (alternative approach)
1276
- ls_files_result = subprocess.run(
1277
- ["git", "-C", working_dir, "ls-files", file_path_for_git],
1278
- capture_output=True,
1279
- text=True
1280
- )
1281
-
1282
- is_tracked = ls_files_result.returncode == 0 and ls_files_result.stdout.strip()
1283
- has_status = git_status_result.returncode == 0
1284
-
1285
- self.logger.info(f"[GIT-STATUS-DEBUG] File tracking status: is_tracked={is_tracked}, has_status={has_status}")
1286
-
1287
- # Success if git knows about the file (either tracked or has status changes)
1288
- if is_tracked or has_status:
1289
- self.logger.info(f"[GIT-STATUS-DEBUG] Git status check successful for {file_path}")
1290
- await self.sio.emit('git_status_response', {
1291
- 'success': True,
1292
- 'file_path': file_path,
1293
- 'working_dir': working_dir,
1294
- 'original_working_dir': original_working_dir,
1295
- 'is_tracked': is_tracked,
1296
- 'has_changes': bool(git_status_result.stdout.strip()) if has_status else False
1297
- }, room=sid)
1298
- else:
1299
- self.logger.info(f"[GIT-STATUS-DEBUG] File {file_path} is not tracked by git")
1300
- await self.sio.emit('git_status_response', {
1301
- 'success': False,
1302
- 'error': 'File is not tracked by git',
1303
- 'file_path': file_path,
1304
- 'working_dir': working_dir,
1305
- 'original_working_dir': original_working_dir,
1306
- 'is_tracked': False
1307
- }, room=sid)
1308
-
1309
- except Exception as e:
1310
- self.logger.error(f"[GIT-STATUS-DEBUG] Exception in check_git_status: {e}")
1311
- import traceback
1312
- self.logger.error(f"[GIT-STATUS-DEBUG] Stack trace: {traceback.format_exc()}")
1313
- await self.sio.emit('git_status_response', {
1314
- 'success': False,
1315
- 'error': str(e),
1316
- 'file_path': data.get('file_path', 'unknown'),
1317
- 'working_dir': data.get('working_dir', 'unknown')
1318
- }, room=sid)
1319
-
1320
- @self.sio.event
1321
- async def git_add_file(sid, data):
1322
- """Add file to git tracking"""
1323
- import subprocess
1324
- try:
1325
- file_path = data.get('file_path')
1326
- working_dir = data.get('working_dir', os.getcwd())
1327
-
1328
- self.logger.info(f"[GIT-ADD-DEBUG] git_add_file called with file_path: {repr(file_path)}, working_dir: {repr(working_dir)} (type: {type(working_dir)})")
1329
-
1330
- if not file_path:
1331
- await self.sio.emit('git_add_response', {
1332
- 'success': False,
1333
- 'error': 'file_path is required',
1334
- 'file_path': file_path
1335
- }, room=sid)
1336
- return
1337
-
1338
- # Validate and sanitize working_dir
1339
- original_working_dir = working_dir
1340
- if not working_dir or working_dir == 'Unknown' or working_dir.strip() == '' or working_dir == '.':
1341
- working_dir = os.getcwd()
1342
- self.logger.info(f"[GIT-ADD-DEBUG] working_dir was invalid ({repr(original_working_dir)}), using cwd: {working_dir}")
1343
- else:
1344
- self.logger.info(f"[GIT-ADD-DEBUG] Using provided working_dir: {working_dir}")
1345
-
1346
- # Validate that the directory exists and is a valid path
1347
- if not os.path.exists(working_dir):
1348
- self.logger.warning(f"[GIT-ADD-DEBUG] Directory does not exist: {working_dir}")
1349
- await self.sio.emit('git_add_response', {
1350
- 'success': False,
1351
- 'error': f'Directory does not exist: {working_dir}',
1352
- 'file_path': file_path,
1353
- 'working_dir': working_dir,
1354
- 'original_working_dir': original_working_dir
1355
- }, room=sid)
1356
- return
1357
-
1358
- if not os.path.isdir(working_dir):
1359
- self.logger.warning(f"[GIT-ADD-DEBUG] Path is not a directory: {working_dir}")
1360
- await self.sio.emit('git_add_response', {
1361
- 'success': False,
1362
- 'error': f'Path is not a directory: {working_dir}',
1363
- 'file_path': file_path,
1364
- 'working_dir': working_dir,
1365
- 'original_working_dir': original_working_dir
1366
- }, room=sid)
1367
- return
1368
-
1369
- self.logger.info(f"[GIT-ADD-DEBUG] Running git add command in directory: {working_dir}")
1370
-
1371
- # Use git add to track the file
1372
- result = subprocess.run(
1373
- ["git", "-C", working_dir, "add", file_path],
1374
- capture_output=True,
1375
- text=True
1376
- )
1377
-
1378
- self.logger.info(f"[GIT-ADD-DEBUG] Git add result: returncode={result.returncode}, stdout={repr(result.stdout)}, stderr={repr(result.stderr)}")
1379
-
1380
- if result.returncode == 0:
1381
- self.logger.info(f"[GIT-ADD-DEBUG] Successfully added {file_path} to git in {working_dir}")
1382
- await self.sio.emit('git_add_response', {
1383
- 'success': True,
1384
- 'file_path': file_path,
1385
- 'working_dir': working_dir,
1386
- 'original_working_dir': original_working_dir,
1387
- 'message': 'File successfully added to git tracking'
1388
- }, room=sid)
1389
- else:
1390
- error_message = result.stderr.strip() or 'Unknown git error'
1391
- self.logger.warning(f"[GIT-ADD-DEBUG] Git add failed: {error_message}")
1392
- await self.sio.emit('git_add_response', {
1393
- 'success': False,
1394
- 'error': f'Git add failed: {error_message}',
1395
- 'file_path': file_path,
1396
- 'working_dir': working_dir,
1397
- 'original_working_dir': original_working_dir
1398
- }, room=sid)
1399
-
1400
- except Exception as e:
1401
- self.logger.error(f"[GIT-ADD-DEBUG] Exception in git_add_file: {e}")
1402
- import traceback
1403
- self.logger.error(f"[GIT-ADD-DEBUG] Stack trace: {traceback.format_exc()}")
1404
- await self.sio.emit('git_add_response', {
1405
- 'success': False,
1406
- 'error': str(e),
1407
- 'file_path': data.get('file_path', 'unknown'),
1408
- 'working_dir': data.get('working_dir', 'unknown')
1409
- }, room=sid)
1410
-
1411
- async def _send_current_status(self, sid: str):
1412
- """Send current system status to a client."""
1413
- try:
1414
- status = {
1415
- "type": "system.status",
1416
- "timestamp": datetime.utcnow().isoformat() + "Z",
1417
- "data": {
1418
- "session_id": self.session_id,
1419
- "session_start": self.session_start,
1420
- "claude_status": self.claude_status,
1421
- "claude_pid": self.claude_pid,
1422
- "connected_clients": len(self.clients),
1423
- "websocket_port": self.port,
1424
- "instance_info": {
1425
- "port": self.port,
1426
- "host": self.host,
1427
- "working_dir": os.getcwd() if self.session_id else None
1428
- }
1429
- }
1430
- }
1431
- await self.sio.emit('claude_event', status, room=sid)
1432
- self.logger.debug("Sent status to client")
1433
- except Exception as e:
1434
- self.logger.error(f"Failed to send status to client: {e}")
1435
- raise
1436
-
1437
- async def _send_event_history(self, sid: str, event_types: list = None, limit: int = 50):
1438
- """Send event history to a specific client.
1439
-
1440
- WHY: When clients connect to the dashboard, they need context from recent events
1441
- to understand what's been happening. This sends the most recent events in
1442
- chronological order (oldest first) so the dashboard displays them properly.
1443
-
1444
- Args:
1445
- sid: Socket.IO session ID of the client
1446
- event_types: Optional list of event types to filter by
1447
- limit: Maximum number of events to send (default: 50)
1448
- """
1449
- try:
1450
- if not self.event_history:
1451
- self.logger.debug(f"No event history to send to client {sid}")
1452
- return
1453
-
1454
- # Limit to reasonable number to avoid overwhelming client
1455
- limit = min(limit, 100)
1456
-
1457
- # Get the most recent events, filtered by type if specified
1458
- history = []
1459
- for event in reversed(self.event_history):
1460
- if not event_types or event.get("type") in event_types:
1461
- history.append(event)
1462
- if len(history) >= limit:
1463
- break
1464
-
1465
- # Reverse to get chronological order (oldest first)
1466
- history = list(reversed(history))
1467
-
1468
- if history:
1469
- # Send as 'history' event that the client expects
1470
- await self.sio.emit('history', {
1471
- "events": history,
1472
- "count": len(history),
1473
- "total_available": len(self.event_history)
1474
- }, room=sid)
1475
-
1476
- self.logger.info(f"📚 Sent {len(history)} historical events to client {sid}")
1477
- else:
1478
- self.logger.debug(f"No matching events found for client {sid} with filters: {event_types}")
1479
-
1480
- except Exception as e:
1481
- self.logger.error(f"❌ Failed to send event history to client {sid}: {e}")
1482
- import traceback
1483
- self.logger.error(f"Stack trace: {traceback.format_exc()}")
1484
-
1485
- def broadcast_event(self, event_type: str, data: Dict[str, Any]):
1486
- """Broadcast an event to all connected clients."""
1487
- if not SOCKETIO_AVAILABLE:
1488
- self.logger.debug(f"⚠️ Socket.IO broadcast skipped - packages not available")
1489
- return
1490
-
1491
- event = {
1492
- "type": event_type,
1493
- "timestamp": datetime.utcnow().isoformat() + "Z",
1494
- "data": data
1495
- }
1496
-
1497
- self.logger.info(f"📤 BROADCASTING EVENT: {event_type}")
1498
- self.logger.debug(f"📄 Event data: {json.dumps(data, indent=2)[:200]}...")
1499
-
1500
- # Store in history
1501
- self.event_history.append(event)
1502
- self.logger.debug(f"📚 Event stored in history (total: {len(self.event_history)})")
1503
-
1504
- # Check if we have clients and event loop
1505
- if not self.clients:
1506
- self.logger.warning(f"⚠️ No Socket.IO clients connected - event will not be delivered")
1507
- return
1508
-
1509
- if not self.loop or not self.sio:
1510
- self.logger.error(f"❌ No event loop or Socket.IO instance available - cannot broadcast event")
1511
- return
1512
-
1513
- self.logger.info(f"🎯 Broadcasting to {len(self.clients)} clients via event loop")
1514
-
1515
- # Broadcast to clients with timeout and error handling
1516
- try:
1517
- # Check if the event loop is still running and not closed
1518
- if self.loop and not self.loop.is_closed() and self.loop.is_running():
1519
- future = asyncio.run_coroutine_threadsafe(
1520
- self.sio.emit('claude_event', event),
1521
- self.loop
1522
- )
1523
- # Wait for completion with timeout to detect issues
1524
- try:
1525
- future.result(timeout=2.0) # 2 second timeout
1526
- self.logger.debug(f"📨 Successfully broadcasted {event_type} to {len(self.clients)} clients")
1527
- except asyncio.TimeoutError:
1528
- self.logger.warning(f"⏰ Broadcast timeout for event {event_type} - continuing anyway")
1529
- except Exception as emit_error:
1530
- self.logger.error(f"❌ Broadcast emit error for {event_type}: {emit_error}")
1531
- else:
1532
- self.logger.warning(f"⚠️ Event loop not available for broadcast of {event_type} - event loop closed or not running")
1533
- except Exception as e:
1534
- self.logger.error(f"❌ Failed to submit broadcast to event loop: {e}")
1535
- import traceback
1536
- self.logger.error(f"Stack trace: {traceback.format_exc()}")
1537
-
1538
- # Convenience methods for common events (same interface as WebSocketServer)
1539
-
1540
- def session_started(self, session_id: str, launch_method: str, working_dir: str):
1541
- """Notify that a session has started."""
1542
- self.session_id = session_id
1543
- self.session_start = datetime.utcnow().isoformat() + "Z"
1544
- self.broadcast_event("session.start", {
1545
- "session_id": session_id,
1546
- "start_time": self.session_start,
1547
- "launch_method": launch_method,
1548
- "working_directory": working_dir,
1549
- "websocket_port": self.port,
1550
- "instance_info": {
1551
- "port": self.port,
1552
- "host": self.host,
1553
- "working_dir": working_dir
1554
- }
1555
- })
1556
-
1557
- def session_ended(self):
1558
- """Notify that a session has ended."""
1559
- if self.session_id:
1560
- duration = None
1561
- if self.session_start:
1562
- start = datetime.fromisoformat(self.session_start.replace("Z", "+00:00"))
1563
- duration = (datetime.utcnow() - start.replace(tzinfo=None)).total_seconds()
1564
-
1565
- self.broadcast_event("session.end", {
1566
- "session_id": self.session_id,
1567
- "end_time": datetime.utcnow().isoformat() + "Z",
1568
- "duration_seconds": duration
1569
- })
1570
-
1571
- self.session_id = None
1572
- self.session_start = None
1573
-
1574
- def claude_status_changed(self, status: str, pid: Optional[int] = None, message: str = ""):
1575
- """Notify Claude status change."""
1576
- self.claude_status = status
1577
- self.claude_pid = pid
1578
- self.broadcast_event("claude.status", {
1579
- "status": status,
1580
- "pid": pid,
1581
- "message": message
1582
- })
1583
-
1584
- def claude_output(self, content: str, stream: str = "stdout"):
1585
- """Broadcast Claude output."""
1586
- self.broadcast_event("claude.output", {
1587
- "content": content,
1588
- "stream": stream
1589
- })
1590
-
1591
- def agent_delegated(self, agent: str, task: str, status: str = "started"):
1592
- """Notify agent delegation."""
1593
- self.broadcast_event("agent.delegation", {
1594
- "agent": agent,
1595
- "task": task,
1596
- "status": status,
1597
- "timestamp": datetime.utcnow().isoformat() + "Z"
1598
- })
1599
-
1600
- def todo_updated(self, todos: List[Dict[str, Any]]):
1601
- """Notify todo list update."""
1602
- stats = {
1603
- "total": len(todos),
1604
- "completed": sum(1 for t in todos if t.get("status") == "completed"),
1605
- "in_progress": sum(1 for t in todos if t.get("status") == "in_progress"),
1606
- "pending": sum(1 for t in todos if t.get("status") == "pending")
1607
- }
1608
-
1609
- self.broadcast_event("todo.update", {
1610
- "todos": todos,
1611
- "stats": stats
1612
- })
1613
-
1614
- def ticket_created(self, ticket_id: str, title: str, priority: str = "medium"):
1615
- """Notify ticket creation."""
1616
- self.broadcast_event("ticket.created", {
1617
- "id": ticket_id,
1618
- "title": title,
1619
- "priority": priority,
1620
- "created_at": datetime.utcnow().isoformat() + "Z"
1621
- })
1622
-
1623
- def memory_loaded(self, agent_id: str, memory_size: int, sections_count: int):
1624
- """Notify when agent memory is loaded from file."""
1625
- self.broadcast_event("memory:loaded", {
1626
- "agent_id": agent_id,
1627
- "memory_size": memory_size,
1628
- "sections_count": sections_count,
1629
- "timestamp": datetime.utcnow().isoformat() + "Z"
1630
- })
1631
-
1632
- def memory_created(self, agent_id: str, template_type: str):
1633
- """Notify when new agent memory is created from template."""
1634
- self.broadcast_event("memory:created", {
1635
- "agent_id": agent_id,
1636
- "template_type": template_type,
1637
- "timestamp": datetime.utcnow().isoformat() + "Z"
1638
- })
1639
-
1640
- def memory_updated(self, agent_id: str, learning_type: str, content: str, section: str):
1641
- """Notify when learning is added to agent memory."""
1642
- self.broadcast_event("memory:updated", {
1643
- "agent_id": agent_id,
1644
- "learning_type": learning_type,
1645
- "content": content,
1646
- "section": section,
1647
- "timestamp": datetime.utcnow().isoformat() + "Z"
1648
- })
1649
-
1650
- def memory_injected(self, agent_id: str, context_size: int):
1651
- """Notify when agent memory is injected into context."""
1652
- self.broadcast_event("memory:injected", {
1653
- "agent_id": agent_id,
1654
- "context_size": context_size,
1655
- "timestamp": datetime.utcnow().isoformat() + "Z"
1656
- })
24
+ # Import from the new modular structure
25
+ from .socketio.client_proxy import SocketIOClientProxy
26
+ from .socketio.server.main import SocketIOServer
1657
27
 
28
+ # Re-export for backward compatibility
29
+ __all__ = [
30
+ "SocketIOClientProxy",
31
+ "SocketIOServer",
32
+ "get_socketio_server",
33
+ "start_socketio_server",
34
+ "stop_socketio_server",
35
+ ]
1658
36
 
1659
37
  # Global instance for easy access
1660
38
  _socketio_server: Optional[SocketIOServer] = None
@@ -1662,7 +40,7 @@ _socketio_server: Optional[SocketIOServer] = None
1662
40
 
1663
41
  def get_socketio_server() -> SocketIOServer:
1664
42
  """Get or create the global Socket.IO server instance.
1665
-
43
+
1666
44
  WHY: In exec mode, a persistent Socket.IO server may already be running
1667
45
  in a separate process. We need to detect this and create a client proxy
1668
46
  instead of trying to start another server.
@@ -1670,11 +48,10 @@ def get_socketio_server() -> SocketIOServer:
1670
48
  global _socketio_server
1671
49
  if _socketio_server is None:
1672
50
  # Check if a Socket.IO server is already running on the default port
1673
- import socket
1674
51
  try:
1675
52
  with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
1676
53
  s.settimeout(0.5)
1677
- result = s.connect_ex(('127.0.0.1', 8765))
54
+ result = s.connect_ex(("127.0.0.1", 8765))
1678
55
  if result == 0:
1679
56
  # Server is already running - create a client proxy
1680
57
  _socketio_server = SocketIOClientProxy(port=8765)
@@ -1684,14 +61,14 @@ def get_socketio_server() -> SocketIOServer:
1684
61
  except Exception:
1685
62
  # On any error, create a real server
1686
63
  _socketio_server = SocketIOServer()
1687
-
64
+
1688
65
  return _socketio_server
1689
66
 
1690
67
 
1691
68
  def start_socketio_server():
1692
69
  """Start the global Socket.IO server."""
1693
70
  server = get_socketio_server()
1694
- server.start()
71
+ server.start_sync()
1695
72
  return server
1696
73
 
1697
74
 
@@ -1699,5 +76,9 @@ def stop_socketio_server():
1699
76
  """Stop the global Socket.IO server."""
1700
77
  global _socketio_server
1701
78
  if _socketio_server:
1702
- _socketio_server.stop()
79
+ _socketio_server.stop_sync()
1703
80
  _socketio_server = None
81
+
82
+
83
+ # All server functionality has been moved to the socketio/ package
84
+ # This file now serves as a compatibility layer that delegates to the modular structure