claude-mpm 5.4.41__py3-none-any.whl → 5.6.72__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 (490) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/CLAUDE_MPM_OUTPUT_STYLE.md +66 -241
  3. claude_mpm/agents/CLAUDE_MPM_RESEARCH_OUTPUT_STYLE.md +413 -0
  4. claude_mpm/agents/CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md +109 -1925
  5. claude_mpm/agents/PM_INSTRUCTIONS.md +161 -298
  6. claude_mpm/agents/WORKFLOW.md +2 -0
  7. claude_mpm/agents/templates/circuit-breakers.md +26 -17
  8. claude_mpm/auth/__init__.py +35 -0
  9. claude_mpm/auth/callback_server.py +328 -0
  10. claude_mpm/auth/models.py +104 -0
  11. claude_mpm/auth/oauth_manager.py +266 -0
  12. claude_mpm/auth/providers/__init__.py +12 -0
  13. claude_mpm/auth/providers/base.py +165 -0
  14. claude_mpm/auth/providers/google.py +261 -0
  15. claude_mpm/auth/token_storage.py +252 -0
  16. claude_mpm/cli/__init__.py +5 -1
  17. claude_mpm/cli/commands/agents.py +2 -4
  18. claude_mpm/cli/commands/agents_reconcile.py +197 -0
  19. claude_mpm/cli/commands/autotodos.py +566 -0
  20. claude_mpm/cli/commands/commander.py +216 -0
  21. claude_mpm/cli/commands/configure.py +620 -21
  22. claude_mpm/cli/commands/configure_agent_display.py +3 -1
  23. claude_mpm/cli/commands/hook_errors.py +60 -60
  24. claude_mpm/cli/commands/mcp.py +29 -17
  25. claude_mpm/cli/commands/mcp_command_router.py +39 -0
  26. claude_mpm/cli/commands/mcp_service_commands.py +304 -0
  27. claude_mpm/cli/commands/monitor.py +2 -2
  28. claude_mpm/cli/commands/mpm_init/core.py +15 -8
  29. claude_mpm/cli/commands/oauth.py +481 -0
  30. claude_mpm/cli/commands/profile.py +9 -10
  31. claude_mpm/cli/commands/run.py +35 -3
  32. claude_mpm/cli/commands/skill_source.py +51 -2
  33. claude_mpm/cli/commands/skills.py +182 -32
  34. claude_mpm/cli/executor.py +129 -16
  35. claude_mpm/cli/helpers.py +1 -1
  36. claude_mpm/cli/interactive/__init__.py +10 -0
  37. claude_mpm/cli/interactive/agent_wizard.py +30 -50
  38. claude_mpm/cli/interactive/questionary_styles.py +65 -0
  39. claude_mpm/cli/interactive/skill_selector.py +481 -0
  40. claude_mpm/cli/parsers/base_parser.py +89 -1
  41. claude_mpm/cli/parsers/commander_parser.py +116 -0
  42. claude_mpm/cli/parsers/mcp_parser.py +79 -0
  43. claude_mpm/cli/parsers/oauth_parser.py +165 -0
  44. claude_mpm/cli/parsers/profile_parser.py +0 -1
  45. claude_mpm/cli/parsers/run_parser.py +10 -0
  46. claude_mpm/cli/parsers/skill_source_parser.py +4 -0
  47. claude_mpm/cli/parsers/skills_parser.py +2 -3
  48. claude_mpm/cli/startup.py +662 -524
  49. claude_mpm/cli/startup_display.py +76 -7
  50. claude_mpm/cli/startup_logging.py +2 -2
  51. claude_mpm/cli/utils.py +7 -3
  52. claude_mpm/commander/__init__.py +78 -0
  53. claude_mpm/commander/adapters/__init__.py +60 -0
  54. claude_mpm/commander/adapters/auggie.py +260 -0
  55. claude_mpm/commander/adapters/base.py +288 -0
  56. claude_mpm/commander/adapters/claude_code.py +392 -0
  57. claude_mpm/commander/adapters/codex.py +237 -0
  58. claude_mpm/commander/adapters/communication.py +366 -0
  59. claude_mpm/commander/adapters/example_usage.py +310 -0
  60. claude_mpm/commander/adapters/mpm.py +389 -0
  61. claude_mpm/commander/adapters/registry.py +204 -0
  62. claude_mpm/commander/api/__init__.py +16 -0
  63. claude_mpm/commander/api/app.py +121 -0
  64. claude_mpm/commander/api/errors.py +133 -0
  65. claude_mpm/commander/api/routes/__init__.py +8 -0
  66. claude_mpm/commander/api/routes/events.py +184 -0
  67. claude_mpm/commander/api/routes/inbox.py +171 -0
  68. claude_mpm/commander/api/routes/messages.py +148 -0
  69. claude_mpm/commander/api/routes/projects.py +271 -0
  70. claude_mpm/commander/api/routes/sessions.py +226 -0
  71. claude_mpm/commander/api/routes/work.py +296 -0
  72. claude_mpm/commander/api/schemas.py +186 -0
  73. claude_mpm/commander/chat/__init__.py +7 -0
  74. claude_mpm/commander/chat/cli.py +149 -0
  75. claude_mpm/commander/chat/commands.py +122 -0
  76. claude_mpm/commander/chat/repl.py +1821 -0
  77. claude_mpm/commander/config.py +51 -0
  78. claude_mpm/commander/config_loader.py +115 -0
  79. claude_mpm/commander/core/__init__.py +10 -0
  80. claude_mpm/commander/core/block_manager.py +325 -0
  81. claude_mpm/commander/core/response_manager.py +323 -0
  82. claude_mpm/commander/daemon.py +603 -0
  83. claude_mpm/commander/env_loader.py +59 -0
  84. claude_mpm/commander/events/__init__.py +26 -0
  85. claude_mpm/commander/events/manager.py +392 -0
  86. claude_mpm/commander/frameworks/__init__.py +12 -0
  87. claude_mpm/commander/frameworks/base.py +233 -0
  88. claude_mpm/commander/frameworks/claude_code.py +58 -0
  89. claude_mpm/commander/frameworks/mpm.py +57 -0
  90. claude_mpm/commander/git/__init__.py +5 -0
  91. claude_mpm/commander/git/worktree_manager.py +212 -0
  92. claude_mpm/commander/inbox/__init__.py +16 -0
  93. claude_mpm/commander/inbox/dedup.py +128 -0
  94. claude_mpm/commander/inbox/inbox.py +224 -0
  95. claude_mpm/commander/inbox/models.py +70 -0
  96. claude_mpm/commander/instance_manager.py +865 -0
  97. claude_mpm/commander/llm/__init__.py +6 -0
  98. claude_mpm/commander/llm/openrouter_client.py +167 -0
  99. claude_mpm/commander/llm/summarizer.py +70 -0
  100. claude_mpm/commander/memory/__init__.py +45 -0
  101. claude_mpm/commander/memory/compression.py +347 -0
  102. claude_mpm/commander/memory/embeddings.py +230 -0
  103. claude_mpm/commander/memory/entities.py +310 -0
  104. claude_mpm/commander/memory/example_usage.py +290 -0
  105. claude_mpm/commander/memory/integration.py +325 -0
  106. claude_mpm/commander/memory/search.py +381 -0
  107. claude_mpm/commander/memory/store.py +657 -0
  108. claude_mpm/commander/models/__init__.py +18 -0
  109. claude_mpm/commander/models/events.py +127 -0
  110. claude_mpm/commander/models/project.py +162 -0
  111. claude_mpm/commander/models/work.py +214 -0
  112. claude_mpm/commander/parsing/__init__.py +20 -0
  113. claude_mpm/commander/parsing/extractor.py +132 -0
  114. claude_mpm/commander/parsing/output_parser.py +270 -0
  115. claude_mpm/commander/parsing/patterns.py +100 -0
  116. claude_mpm/commander/persistence/__init__.py +11 -0
  117. claude_mpm/commander/persistence/event_store.py +274 -0
  118. claude_mpm/commander/persistence/state_store.py +403 -0
  119. claude_mpm/commander/persistence/work_store.py +164 -0
  120. claude_mpm/commander/polling/__init__.py +13 -0
  121. claude_mpm/commander/polling/event_detector.py +104 -0
  122. claude_mpm/commander/polling/output_buffer.py +49 -0
  123. claude_mpm/commander/polling/output_poller.py +153 -0
  124. claude_mpm/commander/project_session.py +268 -0
  125. claude_mpm/commander/proxy/__init__.py +12 -0
  126. claude_mpm/commander/proxy/formatter.py +89 -0
  127. claude_mpm/commander/proxy/output_handler.py +191 -0
  128. claude_mpm/commander/proxy/relay.py +155 -0
  129. claude_mpm/commander/registry.py +410 -0
  130. claude_mpm/commander/runtime/__init__.py +10 -0
  131. claude_mpm/commander/runtime/executor.py +191 -0
  132. claude_mpm/commander/runtime/monitor.py +346 -0
  133. claude_mpm/commander/session/__init__.py +6 -0
  134. claude_mpm/commander/session/context.py +81 -0
  135. claude_mpm/commander/session/manager.py +59 -0
  136. claude_mpm/commander/tmux_orchestrator.py +362 -0
  137. claude_mpm/commander/web/__init__.py +1 -0
  138. claude_mpm/commander/work/__init__.py +30 -0
  139. claude_mpm/commander/work/executor.py +207 -0
  140. claude_mpm/commander/work/queue.py +405 -0
  141. claude_mpm/commander/workflow/__init__.py +27 -0
  142. claude_mpm/commander/workflow/event_handler.py +241 -0
  143. claude_mpm/commander/workflow/notifier.py +146 -0
  144. claude_mpm/commands/mpm-config.md +8 -0
  145. claude_mpm/commands/mpm-doctor.md +8 -0
  146. claude_mpm/commands/mpm-help.md +8 -0
  147. claude_mpm/commands/mpm-init.md +8 -0
  148. claude_mpm/commands/mpm-monitor.md +8 -0
  149. claude_mpm/commands/mpm-organize.md +8 -0
  150. claude_mpm/commands/mpm-postmortem.md +8 -0
  151. claude_mpm/commands/mpm-session-resume.md +9 -1
  152. claude_mpm/commands/mpm-status.md +8 -0
  153. claude_mpm/commands/mpm-ticket-view.md +8 -0
  154. claude_mpm/commands/mpm-version.md +8 -0
  155. claude_mpm/commands/mpm.md +8 -0
  156. claude_mpm/config/agent_presets.py +8 -7
  157. claude_mpm/config/skill_sources.py +16 -0
  158. claude_mpm/constants.py +6 -0
  159. claude_mpm/core/claude_runner.py +154 -2
  160. claude_mpm/core/config.py +35 -22
  161. claude_mpm/core/config_constants.py +74 -9
  162. claude_mpm/core/constants.py +56 -12
  163. claude_mpm/core/hook_manager.py +53 -4
  164. claude_mpm/core/interactive_session.py +12 -11
  165. claude_mpm/core/logger.py +26 -9
  166. claude_mpm/core/logging_utils.py +39 -13
  167. claude_mpm/core/network_config.py +148 -0
  168. claude_mpm/core/oneshot_session.py +7 -6
  169. claude_mpm/core/optimized_startup.py +3 -1
  170. claude_mpm/core/output_style_manager.py +66 -18
  171. claude_mpm/core/shared/config_loader.py +3 -1
  172. claude_mpm/core/socketio_pool.py +47 -15
  173. claude_mpm/core/unified_config.py +54 -8
  174. claude_mpm/core/unified_paths.py +95 -90
  175. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.C33zOoyM.css +1 -0
  176. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.CW1J-YuA.css +1 -0
  177. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/1WZnGYqX.js +24 -0
  178. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/67pF3qNn.js +1 -0
  179. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/6RxdMKe4.js +1 -0
  180. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/8cZrfX0h.js +60 -0
  181. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/9a6T2nm-.js +7 -0
  182. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/B443AUzu.js +1 -0
  183. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/B8AwtY2H.js +1 -0
  184. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BF15LAsF.js +1 -0
  185. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BQaXIfA_.js +331 -0
  186. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BRcwIQNr.js +4 -0
  187. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{uj46x2Wr.js → BSNlmTZj.js} +1 -1
  188. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BV6nKitt.js +43 -0
  189. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BViJ8lZt.js +128 -0
  190. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BcQ-Q0FE.js +1 -0
  191. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Bpyvgze_.js +30 -0
  192. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BzTRqg-z.js +1 -0
  193. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C0Fr8dve.js +1 -0
  194. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C3rbW_a-.js +1 -0
  195. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C8WYN38h.js +1 -0
  196. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C9I8FlXH.js +61 -0
  197. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CIQcWgO2.js +36 -0
  198. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CIctN7YN.js +7 -0
  199. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CKrS_JZW.js +145 -0
  200. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CR6P9C4A.js +89 -0
  201. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CRRR9MD_.js +2 -0
  202. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CRcR2DqT.js +334 -0
  203. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CSXtMOf0.js +1 -0
  204. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CT-sbxSk.js +1 -0
  205. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CWm6DJsp.js +1 -0
  206. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CmKTTxBW.js +1 -0
  207. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CpqQ1Kzn.js +1 -0
  208. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Cu_Erd72.js +261 -0
  209. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/D2nGpDRe.js +1 -0
  210. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/D9iCMida.js +267 -0
  211. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/D9ykgMoY.js +10 -0
  212. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DL2Ldur1.js +1 -0
  213. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DPfltzjH.js +165 -0
  214. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{N4qtv3Hx.js → DR8nis88.js} +2 -2
  215. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DUliQN2b.js +1 -0
  216. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DVp1hx9R.js +1 -0
  217. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DXlhR01x.js +122 -0
  218. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/D_lyTybS.js +1 -0
  219. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DngoTTgh.js +1 -0
  220. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DqkmHtDC.js +220 -0
  221. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DsDh8EYs.js +1 -0
  222. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DypDmXgd.js +139 -0
  223. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Gi6I4Gst.js +1 -0
  224. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/IPYC-LnN.js +162 -0
  225. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/JTLiF7dt.js +24 -0
  226. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/JpevfAFt.js +68 -0
  227. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DjhvlsAc.js → NqQ1dWOy.js} +1 -1
  228. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/R8CEIRAd.js +2 -0
  229. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Zxy7qc-l.js +64 -0
  230. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/q9Hm6zAU.js +1 -0
  231. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/qtd3IeO4.js +15 -0
  232. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/ulBFON_C.js +65 -0
  233. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/wQVh1CoA.js +10 -0
  234. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/app.Dr7t0z2J.js +2 -0
  235. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.BGhZHUS3.js +1 -0
  236. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/{0.CAGBuiOw.js → 0.RgBboRvH.js} +1 -1
  237. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/1.DG-KkbDf.js +1 -0
  238. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.D_jnf-x6.js +1 -0
  239. claude_mpm/dashboard/static/svelte-build/_app/version.json +1 -1
  240. claude_mpm/dashboard/static/svelte-build/index.html +11 -11
  241. claude_mpm/dashboard-svelte/node_modules/katex/src/fonts/generate_fonts.py +58 -0
  242. claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/extract_tfms.py +114 -0
  243. claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/extract_ttfs.py +122 -0
  244. claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/format_json.py +28 -0
  245. claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/parse_tfm.py +211 -0
  246. claude_mpm/experimental/cli_enhancements.py +2 -1
  247. claude_mpm/hooks/claude_hooks/INTEGRATION_EXAMPLE.md +243 -0
  248. claude_mpm/hooks/claude_hooks/README_AUTO_PAUSE.md +403 -0
  249. claude_mpm/hooks/claude_hooks/__pycache__/auto_pause_handler.cpython-311.pyc +0 -0
  250. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-311.pyc +0 -0
  251. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-311.pyc +0 -0
  252. claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-311.pyc +0 -0
  253. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-311.pyc +0 -0
  254. claude_mpm/hooks/claude_hooks/auto_pause_handler.py +485 -0
  255. claude_mpm/hooks/claude_hooks/event_handlers.py +466 -136
  256. claude_mpm/hooks/claude_hooks/hook_handler.py +204 -104
  257. claude_mpm/hooks/claude_hooks/hook_wrapper.sh +6 -11
  258. claude_mpm/hooks/claude_hooks/installer.py +291 -59
  259. claude_mpm/hooks/claude_hooks/memory_integration.py +52 -32
  260. claude_mpm/hooks/claude_hooks/response_tracking.py +43 -60
  261. claude_mpm/hooks/claude_hooks/services/__init__.py +21 -0
  262. claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-311.pyc +0 -0
  263. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-311.pyc +0 -0
  264. claude_mpm/hooks/claude_hooks/services/__pycache__/container.cpython-311.pyc +0 -0
  265. claude_mpm/hooks/claude_hooks/services/__pycache__/protocols.cpython-311.pyc +0 -0
  266. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-311.pyc +0 -0
  267. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-311.pyc +0 -0
  268. claude_mpm/hooks/claude_hooks/services/connection_manager.py +41 -26
  269. claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +38 -105
  270. claude_mpm/hooks/claude_hooks/services/container.py +326 -0
  271. claude_mpm/hooks/claude_hooks/services/protocols.py +328 -0
  272. claude_mpm/hooks/claude_hooks/services/state_manager.py +25 -38
  273. claude_mpm/hooks/claude_hooks/services/subagent_processor.py +75 -77
  274. claude_mpm/hooks/kuzu_memory_hook.py +5 -5
  275. claude_mpm/hooks/session_resume_hook.py +89 -1
  276. claude_mpm/hooks/templates/pre_tool_use_simple.py +6 -6
  277. claude_mpm/hooks/templates/pre_tool_use_template.py +16 -8
  278. claude_mpm/init.py +224 -4
  279. claude_mpm/mcp/__init__.py +9 -0
  280. claude_mpm/mcp/google_workspace_server.py +610 -0
  281. claude_mpm/scripts/claude-hook-handler.sh +46 -19
  282. claude_mpm/services/agents/agent_recommendation_service.py +8 -8
  283. claude_mpm/services/agents/agent_selection_service.py +2 -2
  284. claude_mpm/services/agents/cache_git_manager.py +1 -1
  285. claude_mpm/services/agents/deployment/agent_discovery_service.py +3 -1
  286. claude_mpm/services/agents/deployment/agent_format_converter.py +25 -13
  287. claude_mpm/services/agents/deployment/agent_template_builder.py +37 -17
  288. claude_mpm/services/agents/deployment/async_agent_deployment.py +31 -27
  289. claude_mpm/services/agents/deployment/deployment_reconciler.py +577 -0
  290. claude_mpm/services/agents/deployment/local_template_deployment.py +3 -1
  291. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +36 -8
  292. claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +50 -26
  293. claude_mpm/services/agents/deployment/startup_reconciliation.py +138 -0
  294. claude_mpm/services/agents/git_source_manager.py +21 -2
  295. claude_mpm/services/agents/loading/framework_agent_loader.py +75 -2
  296. claude_mpm/services/agents/single_tier_deployment_service.py +4 -4
  297. claude_mpm/services/agents/sources/git_source_sync_service.py +116 -5
  298. claude_mpm/services/agents/startup_sync.py +5 -2
  299. claude_mpm/services/cli/__init__.py +3 -0
  300. claude_mpm/services/cli/incremental_pause_manager.py +561 -0
  301. claude_mpm/services/cli/session_resume_helper.py +10 -2
  302. claude_mpm/services/command_deployment_service.py +44 -26
  303. claude_mpm/services/delegation_detector.py +175 -0
  304. claude_mpm/services/diagnostics/checks/agent_sources_check.py +30 -0
  305. claude_mpm/services/diagnostics/checks/configuration_check.py +24 -0
  306. claude_mpm/services/diagnostics/checks/installation_check.py +22 -0
  307. claude_mpm/services/diagnostics/checks/mcp_services_check.py +23 -0
  308. claude_mpm/services/diagnostics/doctor_reporter.py +31 -1
  309. claude_mpm/services/diagnostics/models.py +14 -1
  310. claude_mpm/services/event_log.py +325 -0
  311. claude_mpm/services/hook_installer_service.py +77 -8
  312. claude_mpm/services/infrastructure/__init__.py +4 -0
  313. claude_mpm/services/infrastructure/context_usage_tracker.py +291 -0
  314. claude_mpm/services/infrastructure/resume_log_generator.py +24 -5
  315. claude_mpm/services/mcp_config_manager.py +99 -19
  316. claude_mpm/services/mcp_service_registry.py +294 -0
  317. claude_mpm/services/monitor/daemon_manager.py +15 -4
  318. claude_mpm/services/monitor/management/lifecycle.py +8 -3
  319. claude_mpm/services/monitor/server.py +111 -16
  320. claude_mpm/services/pm_skills_deployer.py +302 -94
  321. claude_mpm/services/profile_manager.py +10 -4
  322. claude_mpm/services/skills/git_skill_source_manager.py +192 -29
  323. claude_mpm/services/skills/selective_skill_deployer.py +211 -46
  324. claude_mpm/services/skills/skill_discovery_service.py +74 -4
  325. claude_mpm/services/skills_deployer.py +192 -70
  326. claude_mpm/services/socketio/handlers/hook.py +14 -7
  327. claude_mpm/services/socketio/server/main.py +12 -4
  328. claude_mpm/skills/__init__.py +2 -1
  329. claude_mpm/skills/bundled/collaboration/brainstorming/SKILL.md +79 -0
  330. claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/SKILL.md +178 -0
  331. claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/agent-prompts.md +577 -0
  332. claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/coordination-patterns.md +467 -0
  333. claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/examples.md +537 -0
  334. claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/troubleshooting.md +730 -0
  335. claude_mpm/skills/bundled/collaboration/git-worktrees.md +317 -0
  336. claude_mpm/skills/bundled/collaboration/requesting-code-review/SKILL.md +112 -0
  337. claude_mpm/skills/bundled/collaboration/requesting-code-review/references/code-reviewer-template.md +146 -0
  338. claude_mpm/skills/bundled/collaboration/requesting-code-review/references/review-examples.md +412 -0
  339. claude_mpm/skills/bundled/collaboration/stacked-prs.md +251 -0
  340. claude_mpm/skills/bundled/collaboration/writing-plans/SKILL.md +81 -0
  341. claude_mpm/skills/bundled/collaboration/writing-plans/references/best-practices.md +362 -0
  342. claude_mpm/skills/bundled/collaboration/writing-plans/references/plan-structure-templates.md +312 -0
  343. claude_mpm/skills/bundled/debugging/root-cause-tracing/SKILL.md +152 -0
  344. claude_mpm/skills/bundled/debugging/root-cause-tracing/references/advanced-techniques.md +668 -0
  345. claude_mpm/skills/bundled/debugging/root-cause-tracing/references/examples.md +587 -0
  346. claude_mpm/skills/bundled/debugging/root-cause-tracing/references/integration.md +438 -0
  347. claude_mpm/skills/bundled/debugging/root-cause-tracing/references/tracing-techniques.md +391 -0
  348. claude_mpm/skills/bundled/debugging/systematic-debugging/CREATION-LOG.md +119 -0
  349. claude_mpm/skills/bundled/debugging/systematic-debugging/SKILL.md +148 -0
  350. claude_mpm/skills/bundled/debugging/systematic-debugging/references/anti-patterns.md +483 -0
  351. claude_mpm/skills/bundled/debugging/systematic-debugging/references/examples.md +452 -0
  352. claude_mpm/skills/bundled/debugging/systematic-debugging/references/troubleshooting.md +449 -0
  353. claude_mpm/skills/bundled/debugging/systematic-debugging/references/workflow.md +411 -0
  354. claude_mpm/skills/bundled/debugging/systematic-debugging/test-academic.md +14 -0
  355. claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-1.md +58 -0
  356. claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-2.md +68 -0
  357. claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-3.md +69 -0
  358. claude_mpm/skills/bundled/debugging/verification-before-completion/SKILL.md +131 -0
  359. claude_mpm/skills/bundled/debugging/verification-before-completion/references/gate-function.md +325 -0
  360. claude_mpm/skills/bundled/debugging/verification-before-completion/references/integration-and-workflows.md +490 -0
  361. claude_mpm/skills/bundled/debugging/verification-before-completion/references/red-flags-and-failures.md +425 -0
  362. claude_mpm/skills/bundled/debugging/verification-before-completion/references/verification-patterns.md +499 -0
  363. claude_mpm/skills/bundled/infrastructure/env-manager/INTEGRATION.md +611 -0
  364. claude_mpm/skills/bundled/infrastructure/env-manager/README.md +596 -0
  365. claude_mpm/skills/bundled/infrastructure/env-manager/SKILL.md +260 -0
  366. claude_mpm/skills/bundled/infrastructure/env-manager/examples/nextjs-env-structure.md +315 -0
  367. claude_mpm/skills/bundled/infrastructure/env-manager/references/frameworks.md +436 -0
  368. claude_mpm/skills/bundled/infrastructure/env-manager/references/security.md +433 -0
  369. claude_mpm/skills/bundled/infrastructure/env-manager/references/synchronization.md +452 -0
  370. claude_mpm/skills/bundled/infrastructure/env-manager/references/troubleshooting.md +404 -0
  371. claude_mpm/skills/bundled/infrastructure/env-manager/references/validation.md +420 -0
  372. claude_mpm/skills/bundled/main/artifacts-builder/SKILL.md +86 -0
  373. claude_mpm/skills/bundled/main/internal-comms/SKILL.md +43 -0
  374. claude_mpm/skills/bundled/main/internal-comms/examples/3p-updates.md +47 -0
  375. claude_mpm/skills/bundled/main/internal-comms/examples/company-newsletter.md +65 -0
  376. claude_mpm/skills/bundled/main/internal-comms/examples/faq-answers.md +30 -0
  377. claude_mpm/skills/bundled/main/internal-comms/examples/general-comms.md +16 -0
  378. claude_mpm/skills/bundled/main/mcp-builder/SKILL.md +160 -0
  379. claude_mpm/skills/bundled/main/mcp-builder/reference/design_principles.md +412 -0
  380. claude_mpm/skills/bundled/main/mcp-builder/reference/evaluation.md +602 -0
  381. claude_mpm/skills/bundled/main/mcp-builder/reference/mcp_best_practices.md +915 -0
  382. claude_mpm/skills/bundled/main/mcp-builder/reference/node_mcp_server.md +916 -0
  383. claude_mpm/skills/bundled/main/mcp-builder/reference/python_mcp_server.md +752 -0
  384. claude_mpm/skills/bundled/main/mcp-builder/reference/workflow.md +1237 -0
  385. claude_mpm/skills/bundled/main/skill-creator/SKILL.md +189 -0
  386. claude_mpm/skills/bundled/main/skill-creator/references/best-practices.md +500 -0
  387. claude_mpm/skills/bundled/main/skill-creator/references/creation-workflow.md +464 -0
  388. claude_mpm/skills/bundled/main/skill-creator/references/examples.md +619 -0
  389. claude_mpm/skills/bundled/main/skill-creator/references/progressive-disclosure.md +437 -0
  390. claude_mpm/skills/bundled/main/skill-creator/references/skill-structure.md +231 -0
  391. claude_mpm/skills/bundled/php/espocrm-development/SKILL.md +170 -0
  392. claude_mpm/skills/bundled/php/espocrm-development/references/architecture.md +602 -0
  393. claude_mpm/skills/bundled/php/espocrm-development/references/common-tasks.md +821 -0
  394. claude_mpm/skills/bundled/php/espocrm-development/references/development-workflow.md +742 -0
  395. claude_mpm/skills/bundled/php/espocrm-development/references/frontend-customization.md +726 -0
  396. claude_mpm/skills/bundled/php/espocrm-development/references/hooks-and-services.md +764 -0
  397. claude_mpm/skills/bundled/php/espocrm-development/references/testing-debugging.md +831 -0
  398. claude_mpm/skills/bundled/pm/mpm/SKILL.md +38 -0
  399. claude_mpm/skills/bundled/pm/mpm-agent-update-workflow/SKILL.md +75 -0
  400. claude_mpm/skills/bundled/pm/mpm-bug-reporting/SKILL.md +248 -0
  401. claude_mpm/skills/bundled/pm/mpm-circuit-breaker-enforcement/SKILL.md +476 -0
  402. claude_mpm/skills/bundled/pm/mpm-config/SKILL.md +29 -0
  403. claude_mpm/skills/bundled/pm/mpm-delegation-patterns/SKILL.md +167 -0
  404. claude_mpm/skills/bundled/pm/mpm-doctor/SKILL.md +53 -0
  405. claude_mpm/skills/bundled/pm/mpm-git-file-tracking/SKILL.md +113 -0
  406. claude_mpm/skills/bundled/pm/mpm-help/SKILL.md +35 -0
  407. claude_mpm/skills/bundled/pm/mpm-init/SKILL.md +125 -0
  408. claude_mpm/skills/bundled/pm/mpm-monitor/SKILL.md +32 -0
  409. claude_mpm/skills/bundled/pm/mpm-organize/SKILL.md +121 -0
  410. claude_mpm/skills/bundled/pm/mpm-postmortem/SKILL.md +22 -0
  411. claude_mpm/skills/bundled/pm/mpm-pr-workflow/SKILL.md +124 -0
  412. claude_mpm/skills/bundled/pm/mpm-session-management/SKILL.md +312 -0
  413. claude_mpm/skills/bundled/pm/mpm-session-pause/SKILL.md +170 -0
  414. claude_mpm/skills/bundled/pm/mpm-session-resume/SKILL.md +31 -0
  415. claude_mpm/skills/bundled/pm/mpm-status/SKILL.md +37 -0
  416. claude_mpm/skills/bundled/pm/mpm-teaching-mode/SKILL.md +657 -0
  417. claude_mpm/skills/bundled/pm/mpm-ticket-view/SKILL.md +110 -0
  418. claude_mpm/skills/bundled/pm/mpm-ticketing-integration/SKILL.md +154 -0
  419. claude_mpm/skills/bundled/pm/mpm-tool-usage-guide/SKILL.md +386 -0
  420. claude_mpm/skills/bundled/pm/mpm-verification-protocols/SKILL.md +198 -0
  421. claude_mpm/skills/bundled/pm/mpm-version/SKILL.md +21 -0
  422. claude_mpm/skills/bundled/react/flexlayout-react.md +742 -0
  423. claude_mpm/skills/bundled/rust/desktop-applications/SKILL.md +226 -0
  424. claude_mpm/skills/bundled/rust/desktop-applications/references/architecture-patterns.md +901 -0
  425. claude_mpm/skills/bundled/rust/desktop-applications/references/native-gui-frameworks.md +901 -0
  426. claude_mpm/skills/bundled/rust/desktop-applications/references/platform-integration.md +775 -0
  427. claude_mpm/skills/bundled/rust/desktop-applications/references/state-management.md +937 -0
  428. claude_mpm/skills/bundled/rust/desktop-applications/references/tauri-framework.md +770 -0
  429. claude_mpm/skills/bundled/rust/desktop-applications/references/testing-deployment.md +961 -0
  430. claude_mpm/skills/bundled/security-scanning.md +112 -0
  431. claude_mpm/skills/bundled/tauri/tauri-async-patterns.md +495 -0
  432. claude_mpm/skills/bundled/tauri/tauri-build-deploy.md +599 -0
  433. claude_mpm/skills/bundled/tauri/tauri-command-patterns.md +535 -0
  434. claude_mpm/skills/bundled/tauri/tauri-error-handling.md +613 -0
  435. claude_mpm/skills/bundled/tauri/tauri-event-system.md +648 -0
  436. claude_mpm/skills/bundled/tauri/tauri-file-system.md +673 -0
  437. claude_mpm/skills/bundled/tauri/tauri-frontend-integration.md +767 -0
  438. claude_mpm/skills/bundled/tauri/tauri-performance.md +669 -0
  439. claude_mpm/skills/bundled/tauri/tauri-state-management.md +573 -0
  440. claude_mpm/skills/bundled/tauri/tauri-testing.md +384 -0
  441. claude_mpm/skills/bundled/tauri/tauri-window-management.md +628 -0
  442. claude_mpm/skills/bundled/testing/condition-based-waiting/SKILL.md +119 -0
  443. claude_mpm/skills/bundled/testing/condition-based-waiting/references/patterns-and-implementation.md +253 -0
  444. claude_mpm/skills/bundled/testing/test-driven-development/SKILL.md +145 -0
  445. claude_mpm/skills/bundled/testing/test-driven-development/references/anti-patterns.md +543 -0
  446. claude_mpm/skills/bundled/testing/test-driven-development/references/examples.md +741 -0
  447. claude_mpm/skills/bundled/testing/test-driven-development/references/integration.md +470 -0
  448. claude_mpm/skills/bundled/testing/test-driven-development/references/philosophy.md +458 -0
  449. claude_mpm/skills/bundled/testing/test-driven-development/references/workflow.md +639 -0
  450. claude_mpm/skills/bundled/testing/test-quality-inspector/SKILL.md +458 -0
  451. claude_mpm/skills/bundled/testing/test-quality-inspector/examples/example-inspection-report.md +411 -0
  452. claude_mpm/skills/bundled/testing/test-quality-inspector/references/assertion-quality.md +317 -0
  453. claude_mpm/skills/bundled/testing/test-quality-inspector/references/inspection-checklist.md +270 -0
  454. claude_mpm/skills/bundled/testing/test-quality-inspector/references/red-flags.md +436 -0
  455. claude_mpm/skills/bundled/testing/testing-anti-patterns/SKILL.md +140 -0
  456. claude_mpm/skills/bundled/testing/testing-anti-patterns/references/completeness-anti-patterns.md +572 -0
  457. claude_mpm/skills/bundled/testing/testing-anti-patterns/references/core-anti-patterns.md +411 -0
  458. claude_mpm/skills/bundled/testing/testing-anti-patterns/references/detection-guide.md +569 -0
  459. claude_mpm/skills/bundled/testing/testing-anti-patterns/references/tdd-connection.md +695 -0
  460. claude_mpm/skills/bundled/testing/webapp-testing/SKILL.md +184 -0
  461. claude_mpm/skills/bundled/testing/webapp-testing/decision-tree.md +459 -0
  462. claude_mpm/skills/bundled/testing/webapp-testing/playwright-patterns.md +479 -0
  463. claude_mpm/skills/bundled/testing/webapp-testing/reconnaissance-pattern.md +687 -0
  464. claude_mpm/skills/bundled/testing/webapp-testing/server-management.md +758 -0
  465. claude_mpm/skills/bundled/testing/webapp-testing/troubleshooting.md +868 -0
  466. claude_mpm/skills/registry.py +295 -90
  467. claude_mpm/skills/skill_manager.py +29 -23
  468. claude_mpm/templates/.pre-commit-config.yaml +112 -0
  469. claude_mpm/utils/agent_dependency_loader.py +103 -4
  470. claude_mpm/utils/robust_installer.py +45 -24
  471. claude_mpm-5.6.72.dist-info/METADATA +416 -0
  472. {claude_mpm-5.4.41.dist-info → claude_mpm-5.6.72.dist-info}/RECORD +477 -159
  473. {claude_mpm-5.4.41.dist-info → claude_mpm-5.6.72.dist-info}/WHEEL +1 -1
  474. {claude_mpm-5.4.41.dist-info → claude_mpm-5.6.72.dist-info}/entry_points.txt +2 -0
  475. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.B_FtCwCQ.css +0 -1
  476. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.Cl_eSA4x.css +0 -1
  477. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BgChzWQ1.js +0 -1
  478. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CIXEwuWe.js +0 -1
  479. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CWc5urbQ.js +0 -1
  480. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DMkZpdF2.js +0 -2
  481. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/app.DTL5mJO-.js +0 -2
  482. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.DzuEhzqh.js +0 -1
  483. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/1.DFLC8jdE.js +0 -1
  484. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.DPvEihJJ.js +0 -10
  485. claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-311.pyc +0 -0
  486. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager.cpython-311.pyc +0 -0
  487. claude_mpm-5.4.41.dist-info/METADATA +0 -998
  488. {claude_mpm-5.4.41.dist-info → claude_mpm-5.6.72.dist-info}/licenses/LICENSE +0 -0
  489. {claude_mpm-5.4.41.dist-info → claude_mpm-5.6.72.dist-info}/licenses/LICENSE-FAQ.md +0 -0
  490. {claude_mpm-5.4.41.dist-info → claude_mpm-5.6.72.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,403 @@
1
+ """State persistence for MPM Commander.
2
+
3
+ This module handles atomic persistence and recovery of project registry
4
+ and session states to disk.
5
+ """
6
+
7
+ import asyncio
8
+ import json
9
+ import logging
10
+ import tempfile
11
+ from datetime import datetime, timezone
12
+ from pathlib import Path
13
+ from typing import Any, Dict, List, Optional
14
+
15
+ from ..frameworks.base import RegisteredInstance
16
+ from ..models import Project, ProjectState, ToolSession
17
+ from ..registry import ProjectRegistry
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ class StateStore:
23
+ """Persists and recovers project registry state.
24
+
25
+ Provides atomic writes to prevent corruption and handles graceful
26
+ recovery from missing or corrupted files.
27
+
28
+ Attributes:
29
+ state_dir: Directory for state files
30
+ projects_path: Path to projects.json
31
+ sessions_path: Path to sessions.json
32
+
33
+ Example:
34
+ >>> store = StateStore(Path("~/.claude-mpm/commander"))
35
+ >>> await store.save_projects(registry)
36
+ >>> projects = await store.load_projects()
37
+ """
38
+
39
+ VERSION = "1.0"
40
+
41
+ def __init__(self, state_dir: Path):
42
+ """Initialize state store.
43
+
44
+ Args:
45
+ state_dir: Directory for state files (created if needed)
46
+ """
47
+ self.state_dir = state_dir.expanduser()
48
+ self.state_dir.mkdir(parents=True, exist_ok=True)
49
+
50
+ self.projects_path = self.state_dir / "projects.json"
51
+ self.sessions_path = self.state_dir / "sessions.json"
52
+ self.instances_path = self.state_dir / "instances.json"
53
+
54
+ logger.info(f"Initialized StateStore at {self.state_dir}")
55
+
56
+ async def save_projects(self, registry: ProjectRegistry) -> None:
57
+ """Save all projects to disk (atomic write).
58
+
59
+ Args:
60
+ registry: ProjectRegistry to persist
61
+
62
+ Raises:
63
+ IOError: If write fails
64
+ """
65
+ projects = registry.list_all()
66
+
67
+ data = {
68
+ "version": self.VERSION,
69
+ "saved_at": datetime.now(timezone.utc).isoformat(),
70
+ "projects": [self._serialize_project(p) for p in projects],
71
+ }
72
+
73
+ # Run sync I/O in executor to avoid blocking
74
+ await asyncio.get_event_loop().run_in_executor(
75
+ None, self._atomic_write, self.projects_path, data
76
+ )
77
+
78
+ logger.info(f"Saved {len(projects)} projects to {self.projects_path}")
79
+
80
+ async def load_projects(self) -> List[Project]:
81
+ """Load projects from disk.
82
+
83
+ Returns:
84
+ List of Project instances (empty if file missing or corrupt)
85
+ """
86
+ if not self.projects_path.exists():
87
+ logger.info("No projects file found, returning empty list")
88
+ return []
89
+
90
+ try:
91
+ # Run sync I/O in executor
92
+ data = await asyncio.get_event_loop().run_in_executor(
93
+ None, self._read_json, self.projects_path
94
+ )
95
+
96
+ if data.get("version") != self.VERSION:
97
+ logger.warning(
98
+ f"Version mismatch: expected {self.VERSION}, "
99
+ f"got {data.get('version')}"
100
+ )
101
+
102
+ projects = [self._deserialize_project(p) for p in data.get("projects", [])]
103
+
104
+ logger.info(f"Loaded {len(projects)} projects from {self.projects_path}")
105
+ return projects
106
+
107
+ except Exception as e:
108
+ logger.error(f"Failed to load projects: {e}", exc_info=True)
109
+ return []
110
+
111
+ async def save_sessions(self, sessions: Dict[str, Any]) -> None:
112
+ """Save session states (for recovery).
113
+
114
+ Args:
115
+ sessions: Active ProjectSession instances by project_id
116
+
117
+ Raises:
118
+ IOError: If write fails
119
+ """
120
+ data = {
121
+ "version": self.VERSION,
122
+ "saved_at": datetime.now(timezone.utc).isoformat(),
123
+ "sessions": {
124
+ project_id: {
125
+ "state": session.state.value,
126
+ "pane_target": session.active_pane,
127
+ "paused_event_id": session.pause_reason,
128
+ }
129
+ for project_id, session in sessions.items()
130
+ },
131
+ }
132
+
133
+ # Run sync I/O in executor
134
+ await asyncio.get_event_loop().run_in_executor(
135
+ None, self._atomic_write, self.sessions_path, data
136
+ )
137
+
138
+ logger.info(f"Saved {len(sessions)} sessions to {self.sessions_path}")
139
+
140
+ async def load_sessions(self) -> Dict[str, Dict[str, Any]]:
141
+ """Load session states.
142
+
143
+ Returns:
144
+ Dict mapping project_id to session state dict
145
+ (empty if file missing or corrupt)
146
+ """
147
+ if not self.sessions_path.exists():
148
+ logger.info("No sessions file found, returning empty dict")
149
+ return {}
150
+
151
+ try:
152
+ # Run sync I/O in executor
153
+ data = await asyncio.get_event_loop().run_in_executor(
154
+ None, self._read_json, self.sessions_path
155
+ )
156
+
157
+ if data.get("version") != self.VERSION:
158
+ logger.warning(
159
+ f"Version mismatch: expected {self.VERSION}, "
160
+ f"got {data.get('version')}"
161
+ )
162
+
163
+ sessions = data.get("sessions", {})
164
+ logger.info(f"Loaded {len(sessions)} sessions from {self.sessions_path}")
165
+ return sessions
166
+
167
+ except Exception as e:
168
+ logger.error(f"Failed to load sessions: {e}", exc_info=True)
169
+ return {}
170
+
171
+ def _atomic_write(self, path: Path, data: Dict) -> None:
172
+ """Write atomically (write to temp, then rename).
173
+
174
+ Args:
175
+ path: Target file path
176
+ data: Data to serialize as JSON
177
+
178
+ Raises:
179
+ IOError: If write fails
180
+ """
181
+ # Write to temporary file in same directory
182
+ # (ensures atomic rename works across filesystems)
183
+ fd, tmp_path = tempfile.mkstemp(
184
+ dir=path.parent, prefix=f".{path.name}.", suffix=".tmp"
185
+ )
186
+
187
+ try:
188
+ with open(fd, "w") as f:
189
+ json.dump(data, f, indent=2)
190
+
191
+ # Atomic rename (POSIX guarantees atomicity)
192
+ Path(tmp_path).rename(path)
193
+
194
+ logger.debug(f"Atomically wrote to {path}")
195
+
196
+ except Exception as e:
197
+ # Clean up temp file on error
198
+ try:
199
+ Path(tmp_path).unlink()
200
+ except Exception: # nosec B110
201
+ pass # Ignore errors during cleanup
202
+ raise OSError(f"Failed to write {path}: {e}") from e
203
+
204
+ def _read_json(self, path: Path) -> Dict:
205
+ """Read JSON file.
206
+
207
+ Args:
208
+ path: File to read
209
+
210
+ Returns:
211
+ Parsed JSON data
212
+
213
+ Raises:
214
+ IOError: If read fails
215
+ """
216
+ with open(path) as f:
217
+ return json.load(f)
218
+
219
+ def _serialize_project(self, project: Project) -> Dict[str, Any]:
220
+ """Serialize Project to JSON-compatible dict.
221
+
222
+ Args:
223
+ project: Project instance
224
+
225
+ Returns:
226
+ JSON-serializable dict
227
+ """
228
+ return {
229
+ "id": project.id,
230
+ "path": project.path,
231
+ "name": project.name,
232
+ "state": project.state.value,
233
+ "state_reason": project.state_reason,
234
+ "config_loaded": project.config_loaded,
235
+ "config": project.config,
236
+ "sessions": {
237
+ sid: self._serialize_session(session)
238
+ for sid, session in project.sessions.items()
239
+ },
240
+ "created_at": project.created_at.isoformat(),
241
+ "last_activity": project.last_activity.isoformat(),
242
+ }
243
+
244
+ def _serialize_session(self, session: ToolSession) -> Dict[str, Any]:
245
+ """Serialize ToolSession to JSON-compatible dict.
246
+
247
+ Args:
248
+ session: ToolSession instance
249
+
250
+ Returns:
251
+ JSON-serializable dict
252
+ """
253
+ return {
254
+ "id": session.id,
255
+ "project_id": session.project_id,
256
+ "runtime": session.runtime,
257
+ "tmux_target": session.tmux_target,
258
+ "status": session.status,
259
+ "created_at": session.created_at.isoformat(),
260
+ "last_output_at": (
261
+ session.last_output_at.isoformat() if session.last_output_at else None
262
+ ),
263
+ }
264
+
265
+ def _deserialize_project(self, data: Dict[str, Any]) -> Project:
266
+ """Deserialize Project from JSON dict.
267
+
268
+ Args:
269
+ data: Serialized project data
270
+
271
+ Returns:
272
+ Project instance
273
+ """
274
+ return Project(
275
+ id=data["id"],
276
+ path=data["path"],
277
+ name=data["name"],
278
+ state=ProjectState(data["state"]),
279
+ state_reason=data.get("state_reason"),
280
+ config_loaded=data.get("config_loaded", False),
281
+ config=data.get("config"),
282
+ sessions={
283
+ sid: self._deserialize_session(sess)
284
+ for sid, sess in data.get("sessions", {}).items()
285
+ },
286
+ created_at=datetime.fromisoformat(data["created_at"]),
287
+ last_activity=datetime.fromisoformat(data["last_activity"]),
288
+ )
289
+
290
+ def _deserialize_session(self, data: Dict[str, Any]) -> ToolSession:
291
+ """Deserialize ToolSession from JSON dict.
292
+
293
+ Args:
294
+ data: Serialized session data
295
+
296
+ Returns:
297
+ ToolSession instance
298
+ """
299
+ return ToolSession(
300
+ id=data["id"],
301
+ project_id=data["project_id"],
302
+ runtime=data["runtime"],
303
+ tmux_target=data["tmux_target"],
304
+ status=data.get("status", "initializing"),
305
+ created_at=datetime.fromisoformat(data["created_at"]),
306
+ last_output_at=(
307
+ datetime.fromisoformat(data["last_output_at"])
308
+ if data.get("last_output_at")
309
+ else None
310
+ ),
311
+ )
312
+
313
+ # Instance persistence methods
314
+
315
+ def save_instances(self, instances: Dict[str, RegisteredInstance]) -> None:
316
+ """Save registered instances to disk.
317
+
318
+ Args:
319
+ instances: Dict mapping instance name to RegisteredInstance
320
+
321
+ Raises:
322
+ IOError: If write fails
323
+ """
324
+ data = {
325
+ "version": self.VERSION,
326
+ "saved_at": datetime.now(timezone.utc).isoformat(),
327
+ "instances": {name: inst.to_dict() for name, inst in instances.items()},
328
+ }
329
+ self._atomic_write(self.instances_path, data)
330
+ logger.info(f"Saved {len(instances)} instances to {self.instances_path}")
331
+
332
+ def load_instances(self) -> Dict[str, RegisteredInstance]:
333
+ """Load registered instances from disk.
334
+
335
+ Returns:
336
+ Dict mapping instance name to RegisteredInstance
337
+ (empty if file missing or corrupt)
338
+ """
339
+ if not self.instances_path.exists():
340
+ logger.info("No instances file found, returning empty dict")
341
+ return {}
342
+
343
+ try:
344
+ data = self._read_json(self.instances_path)
345
+
346
+ if data.get("version") != self.VERSION:
347
+ logger.warning(
348
+ f"Version mismatch: expected {self.VERSION}, "
349
+ f"got {data.get('version')}"
350
+ )
351
+
352
+ instances = {
353
+ name: RegisteredInstance.from_dict(inst_data)
354
+ for name, inst_data in data.get("instances", {}).items()
355
+ }
356
+
357
+ logger.info(f"Loaded {len(instances)} instances from {self.instances_path}")
358
+ return instances
359
+
360
+ except Exception as e:
361
+ logger.error(f"Failed to load instances: {e}", exc_info=True)
362
+ return {}
363
+
364
+ def register_instance(self, instance: RegisteredInstance) -> None:
365
+ """Register a single instance (add to existing).
366
+
367
+ Args:
368
+ instance: RegisteredInstance to add
369
+ """
370
+ instances = self.load_instances()
371
+ instances[instance.name] = instance
372
+ self.save_instances(instances)
373
+ logger.info(f"Registered instance '{instance.name}'")
374
+
375
+ def unregister_instance(self, name: str) -> bool:
376
+ """Remove an instance registration.
377
+
378
+ Args:
379
+ name: Instance name to remove
380
+
381
+ Returns:
382
+ True if instance was found and removed, False if not found
383
+ """
384
+ instances = self.load_instances()
385
+ if name in instances:
386
+ del instances[name]
387
+ self.save_instances(instances)
388
+ logger.info(f"Unregistered instance '{name}'")
389
+ return True
390
+ logger.warning(f"Instance '{name}' not found for unregistration")
391
+ return False
392
+
393
+ def get_registered_instance(self, name: str) -> Optional[RegisteredInstance]:
394
+ """Get a single registered instance by name.
395
+
396
+ Args:
397
+ name: Instance name to look up
398
+
399
+ Returns:
400
+ RegisteredInstance if found, None otherwise
401
+ """
402
+ instances = self.load_instances()
403
+ return instances.get(name)
@@ -0,0 +1,164 @@
1
+ """Work item persistence for MPM Commander.
2
+
3
+ This module handles persistence and recovery of work queues,
4
+ including all work items across all projects.
5
+ """
6
+
7
+ import asyncio
8
+ import json
9
+ import logging
10
+ import tempfile
11
+ from pathlib import Path
12
+ from typing import Any, Dict, List
13
+
14
+ from ..models.work import WorkItem
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ class WorkStore:
20
+ """Persists and recovers work items.
21
+
22
+ Provides efficient work item persistence with:
23
+ - Batch save of all work items across all projects
24
+ - Atomic writes to prevent corruption
25
+ - Schema validation on load
26
+
27
+ Attributes:
28
+ state_dir: Directory for state files
29
+ work_path: Path to work.json
30
+
31
+ Example:
32
+ >>> store = WorkStore(Path("~/.claude-mpm/commander"))
33
+ >>> await store.save_work(work_queues)
34
+ >>> work_items = await store.load_work()
35
+ """
36
+
37
+ VERSION = "1.0"
38
+
39
+ def __init__(self, state_dir: Path):
40
+ """Initialize work store.
41
+
42
+ Args:
43
+ state_dir: Directory for state files (created if needed)
44
+ """
45
+ self.state_dir = state_dir.expanduser()
46
+ self.state_dir.mkdir(parents=True, exist_ok=True)
47
+
48
+ self.work_path = self.state_dir / "work.json"
49
+
50
+ logger.info(f"Initialized WorkStore at {self.state_dir}")
51
+
52
+ async def save_work(self, work_queues: Dict[str, "WorkQueue"]) -> None: # noqa: F821
53
+ """Save work items from all queues to disk.
54
+
55
+ Args:
56
+ work_queues: Dict of project_id -> WorkQueue
57
+
58
+ Raises:
59
+ IOError: If write fails
60
+ """
61
+ # Collect all work items from all queues
62
+ all_items: List[WorkItem] = []
63
+ for queue in work_queues.values():
64
+ all_items.extend(queue.list())
65
+
66
+ data = {
67
+ "version": self.VERSION,
68
+ "saved_at": self._get_timestamp(),
69
+ "work_items": [item.to_dict() for item in all_items],
70
+ }
71
+
72
+ # Run sync I/O in executor
73
+ await asyncio.get_event_loop().run_in_executor(
74
+ None, self._atomic_write, self.work_path, data
75
+ )
76
+
77
+ logger.info(
78
+ f"Saved {len(all_items)} work items from {len(work_queues)} "
79
+ f"queues to {self.work_path}"
80
+ )
81
+
82
+ async def load_work(self) -> List[WorkItem]:
83
+ """Load work items from disk.
84
+
85
+ Returns:
86
+ List of WorkItem instances (empty if file missing or corrupt)
87
+ """
88
+ if not self.work_path.exists():
89
+ logger.info("No work file found, returning empty list")
90
+ return []
91
+
92
+ try:
93
+ # Run sync I/O in executor
94
+ data = await asyncio.get_event_loop().run_in_executor(
95
+ None, self._read_json, self.work_path
96
+ )
97
+
98
+ if data.get("version") != self.VERSION:
99
+ logger.warning(
100
+ f"Version mismatch: expected {self.VERSION}, "
101
+ f"got {data.get('version')}"
102
+ )
103
+
104
+ # Deserialize work items
105
+ items = []
106
+ for item_data in data.get("work_items", []):
107
+ try:
108
+ item = WorkItem.from_dict(item_data)
109
+ items.append(item)
110
+ except Exception as e:
111
+ logger.error(f"Failed to deserialize work item: {e}")
112
+ logger.debug(f"Item data: {item_data}")
113
+
114
+ logger.info(f"Loaded {len(items)} work items from {self.work_path}")
115
+ return items
116
+
117
+ except json.JSONDecodeError as e:
118
+ logger.error(f"Failed to parse work file: {e}")
119
+ return []
120
+ except Exception as e:
121
+ logger.error(f"Failed to load work items: {e}")
122
+ return []
123
+
124
+ def _atomic_write(self, path: Path, data: Dict[str, Any]) -> None:
125
+ """Write data to file atomically.
126
+
127
+ Uses temp file + rename to ensure atomic write.
128
+
129
+ Args:
130
+ path: Target file path
131
+ data: Dictionary to write as JSON
132
+ """
133
+ # Write to temp file
134
+ with tempfile.NamedTemporaryFile(
135
+ mode="w",
136
+ dir=path.parent,
137
+ prefix=".work-",
138
+ suffix=".tmp",
139
+ delete=False,
140
+ ) as tmp:
141
+ json.dump(data, tmp, indent=2, default=str)
142
+ tmp_path = Path(tmp.name)
143
+
144
+ # Atomic rename
145
+ tmp_path.replace(path)
146
+ logger.debug(f"Wrote work file atomically: {path}")
147
+
148
+ def _read_json(self, path: Path) -> Dict[str, Any]:
149
+ """Read JSON file.
150
+
151
+ Args:
152
+ path: File path
153
+
154
+ Returns:
155
+ Parsed JSON data
156
+ """
157
+ with open(path) as f:
158
+ return json.load(f)
159
+
160
+ def _get_timestamp(self) -> str:
161
+ """Get current timestamp as ISO string."""
162
+ from datetime import datetime, timezone
163
+
164
+ return datetime.now(timezone.utc).isoformat()
@@ -0,0 +1,13 @@
1
+ """Output polling and event detection for MPM Commander."""
2
+
3
+ from .event_detector import BasicEventDetector, DetectedEvent, EventType
4
+ from .output_buffer import OutputBuffer
5
+ from .output_poller import OutputPoller
6
+
7
+ __all__ = [
8
+ "BasicEventDetector",
9
+ "DetectedEvent",
10
+ "EventType",
11
+ "OutputBuffer",
12
+ "OutputPoller",
13
+ ]
@@ -0,0 +1,104 @@
1
+ """Event detection for MPM Commander output."""
2
+
3
+ import re
4
+ from dataclasses import dataclass
5
+ from enum import Enum
6
+ from typing import Optional
7
+
8
+
9
+ class EventType(Enum):
10
+ """Types of events that can be detected in output."""
11
+
12
+ ERROR = "error"
13
+ IDLE = "idle"
14
+
15
+
16
+ @dataclass
17
+ class DetectedEvent:
18
+ """An event detected in session output."""
19
+
20
+ event_type: EventType
21
+ content: str
22
+ context: str # Surrounding lines for context
23
+ line_number: Optional[int] = None
24
+
25
+
26
+ class BasicEventDetector:
27
+ """Phase 1: Detect errors and idle states only."""
28
+
29
+ # ANSI escape code pattern
30
+ ANSI_ESCAPE = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])")
31
+
32
+ ERROR_PATTERNS = [
33
+ r"Error:",
34
+ r"Failed:",
35
+ r"Exception:",
36
+ r"Traceback \(most recent call last\):",
37
+ r"Permission denied",
38
+ r"command not found",
39
+ r"FATAL:",
40
+ r"✗", # Claude Code error indicator
41
+ ]
42
+
43
+ IDLE_PATTERNS = [
44
+ r"^>\s*$", # Claude Code prompt
45
+ r"^claude>\s*$", # Alternative prompt
46
+ r"^\$\s*$", # Shell prompt
47
+ r"What would you like",
48
+ ]
49
+
50
+ def strip_ansi(self, text: str) -> str:
51
+ """Remove ANSI escape codes from text.
52
+
53
+ Args:
54
+ text: Text potentially containing ANSI codes
55
+
56
+ Returns:
57
+ Clean text with ANSI codes removed
58
+ """
59
+ return self.ANSI_ESCAPE.sub("", text)
60
+
61
+ def detect_error(self, content: str) -> Optional[DetectedEvent]:
62
+ """Check for error patterns in output.
63
+
64
+ Args:
65
+ content: Output content to check
66
+
67
+ Returns:
68
+ DetectedEvent if error found, None otherwise
69
+ """
70
+ clean = self.strip_ansi(content)
71
+ lines = clean.split("\n")
72
+
73
+ for i, line in enumerate(lines):
74
+ for pattern in self.ERROR_PATTERNS:
75
+ if re.search(pattern, line, re.IGNORECASE):
76
+ # Extract context (3 lines before/after)
77
+ start = max(0, i - 3)
78
+ end = min(len(lines), i + 4)
79
+ context = "\n".join(lines[start:end])
80
+
81
+ return DetectedEvent(
82
+ event_type=EventType.ERROR,
83
+ content=line.strip(),
84
+ context=context,
85
+ line_number=i + 1,
86
+ )
87
+ return None
88
+
89
+ def detect_idle(self, content: str) -> bool:
90
+ """Check if tool is waiting for input (last line is a prompt).
91
+
92
+ Args:
93
+ content: Output content to check
94
+
95
+ Returns:
96
+ True if session appears to be idle (waiting for input)
97
+ """
98
+ clean = self.strip_ansi(content)
99
+ lines = clean.strip().split("\n")
100
+ if not lines:
101
+ return False
102
+
103
+ last_line = lines[-1]
104
+ return any(re.search(p, last_line) for p in self.IDLE_PATTERNS)