claude-mpm 5.4.65__py3-none-any.whl → 5.6.10__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 (313) 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 +107 -1928
  5. claude_mpm/agents/PM_INSTRUCTIONS.md +119 -689
  6. claude_mpm/agents/WORKFLOW.md +2 -0
  7. claude_mpm/agents/templates/circuit-breakers.md +26 -17
  8. claude_mpm/cli/__init__.py +5 -1
  9. claude_mpm/cli/commands/agents.py +2 -4
  10. claude_mpm/cli/commands/agents_reconcile.py +197 -0
  11. claude_mpm/cli/commands/autotodos.py +566 -0
  12. claude_mpm/cli/commands/commander.py +46 -0
  13. claude_mpm/cli/commands/configure.py +620 -21
  14. claude_mpm/cli/commands/hook_errors.py +60 -60
  15. claude_mpm/cli/commands/monitor.py +2 -2
  16. claude_mpm/cli/commands/mpm_init/core.py +2 -2
  17. claude_mpm/cli/commands/run.py +35 -3
  18. claude_mpm/cli/commands/skill_source.py +51 -2
  19. claude_mpm/cli/commands/skills.py +171 -17
  20. claude_mpm/cli/executor.py +120 -16
  21. claude_mpm/cli/interactive/__init__.py +10 -0
  22. claude_mpm/cli/interactive/agent_wizard.py +30 -50
  23. claude_mpm/cli/interactive/questionary_styles.py +65 -0
  24. claude_mpm/cli/interactive/skill_selector.py +481 -0
  25. claude_mpm/cli/parsers/base_parser.py +76 -1
  26. claude_mpm/cli/parsers/commander_parser.py +83 -0
  27. claude_mpm/cli/parsers/run_parser.py +10 -0
  28. claude_mpm/cli/parsers/skill_source_parser.py +4 -0
  29. claude_mpm/cli/parsers/skills_parser.py +5 -0
  30. claude_mpm/cli/startup.py +203 -359
  31. claude_mpm/cli/startup_display.py +72 -5
  32. claude_mpm/cli/startup_logging.py +2 -2
  33. claude_mpm/cli/utils.py +7 -3
  34. claude_mpm/commander/__init__.py +72 -0
  35. claude_mpm/commander/adapters/__init__.py +31 -0
  36. claude_mpm/commander/adapters/base.py +191 -0
  37. claude_mpm/commander/adapters/claude_code.py +361 -0
  38. claude_mpm/commander/adapters/communication.py +366 -0
  39. claude_mpm/commander/api/__init__.py +16 -0
  40. claude_mpm/commander/api/app.py +105 -0
  41. claude_mpm/commander/api/errors.py +133 -0
  42. claude_mpm/commander/api/routes/__init__.py +8 -0
  43. claude_mpm/commander/api/routes/events.py +184 -0
  44. claude_mpm/commander/api/routes/inbox.py +171 -0
  45. claude_mpm/commander/api/routes/messages.py +148 -0
  46. claude_mpm/commander/api/routes/projects.py +271 -0
  47. claude_mpm/commander/api/routes/sessions.py +228 -0
  48. claude_mpm/commander/api/routes/work.py +260 -0
  49. claude_mpm/commander/api/schemas.py +182 -0
  50. claude_mpm/commander/chat/__init__.py +7 -0
  51. claude_mpm/commander/chat/cli.py +107 -0
  52. claude_mpm/commander/chat/commands.py +96 -0
  53. claude_mpm/commander/chat/repl.py +310 -0
  54. claude_mpm/commander/config.py +49 -0
  55. claude_mpm/commander/config_loader.py +115 -0
  56. claude_mpm/commander/daemon.py +398 -0
  57. claude_mpm/commander/events/__init__.py +26 -0
  58. claude_mpm/commander/events/manager.py +332 -0
  59. claude_mpm/commander/frameworks/__init__.py +12 -0
  60. claude_mpm/commander/frameworks/base.py +143 -0
  61. claude_mpm/commander/frameworks/claude_code.py +58 -0
  62. claude_mpm/commander/frameworks/mpm.py +62 -0
  63. claude_mpm/commander/inbox/__init__.py +16 -0
  64. claude_mpm/commander/inbox/dedup.py +128 -0
  65. claude_mpm/commander/inbox/inbox.py +224 -0
  66. claude_mpm/commander/inbox/models.py +70 -0
  67. claude_mpm/commander/instance_manager.py +337 -0
  68. claude_mpm/commander/llm/__init__.py +6 -0
  69. claude_mpm/commander/llm/openrouter_client.py +167 -0
  70. claude_mpm/commander/llm/summarizer.py +70 -0
  71. claude_mpm/commander/models/__init__.py +18 -0
  72. claude_mpm/commander/models/events.py +121 -0
  73. claude_mpm/commander/models/project.py +162 -0
  74. claude_mpm/commander/models/work.py +214 -0
  75. claude_mpm/commander/parsing/__init__.py +20 -0
  76. claude_mpm/commander/parsing/extractor.py +132 -0
  77. claude_mpm/commander/parsing/output_parser.py +270 -0
  78. claude_mpm/commander/parsing/patterns.py +100 -0
  79. claude_mpm/commander/persistence/__init__.py +11 -0
  80. claude_mpm/commander/persistence/event_store.py +274 -0
  81. claude_mpm/commander/persistence/state_store.py +309 -0
  82. claude_mpm/commander/persistence/work_store.py +164 -0
  83. claude_mpm/commander/polling/__init__.py +13 -0
  84. claude_mpm/commander/polling/event_detector.py +104 -0
  85. claude_mpm/commander/polling/output_buffer.py +49 -0
  86. claude_mpm/commander/polling/output_poller.py +153 -0
  87. claude_mpm/commander/project_session.py +268 -0
  88. claude_mpm/commander/proxy/__init__.py +12 -0
  89. claude_mpm/commander/proxy/formatter.py +89 -0
  90. claude_mpm/commander/proxy/output_handler.py +191 -0
  91. claude_mpm/commander/proxy/relay.py +155 -0
  92. claude_mpm/commander/registry.py +404 -0
  93. claude_mpm/commander/runtime/__init__.py +10 -0
  94. claude_mpm/commander/runtime/executor.py +191 -0
  95. claude_mpm/commander/runtime/monitor.py +316 -0
  96. claude_mpm/commander/session/__init__.py +6 -0
  97. claude_mpm/commander/session/context.py +81 -0
  98. claude_mpm/commander/session/manager.py +59 -0
  99. claude_mpm/commander/tmux_orchestrator.py +361 -0
  100. claude_mpm/commander/web/__init__.py +1 -0
  101. claude_mpm/commander/work/__init__.py +30 -0
  102. claude_mpm/commander/work/executor.py +189 -0
  103. claude_mpm/commander/work/queue.py +405 -0
  104. claude_mpm/commander/workflow/__init__.py +27 -0
  105. claude_mpm/commander/workflow/event_handler.py +219 -0
  106. claude_mpm/commander/workflow/notifier.py +146 -0
  107. claude_mpm/commands/mpm-config.md +8 -0
  108. claude_mpm/commands/mpm-doctor.md +8 -0
  109. claude_mpm/commands/mpm-help.md +8 -0
  110. claude_mpm/commands/mpm-init.md +8 -0
  111. claude_mpm/commands/mpm-monitor.md +8 -0
  112. claude_mpm/commands/mpm-organize.md +8 -0
  113. claude_mpm/commands/mpm-postmortem.md +8 -0
  114. claude_mpm/commands/mpm-session-resume.md +9 -1
  115. claude_mpm/commands/mpm-status.md +8 -0
  116. claude_mpm/commands/mpm-ticket-view.md +8 -0
  117. claude_mpm/commands/mpm-version.md +8 -0
  118. claude_mpm/commands/mpm.md +8 -0
  119. claude_mpm/config/agent_presets.py +8 -7
  120. claude_mpm/config/skill_sources.py +16 -0
  121. claude_mpm/constants.py +1 -0
  122. claude_mpm/core/claude_runner.py +2 -2
  123. claude_mpm/core/config.py +32 -19
  124. claude_mpm/core/hook_manager.py +51 -3
  125. claude_mpm/core/interactive_session.py +7 -7
  126. claude_mpm/core/logger.py +26 -9
  127. claude_mpm/core/logging_utils.py +35 -11
  128. claude_mpm/core/output_style_manager.py +31 -13
  129. claude_mpm/core/unified_config.py +54 -8
  130. claude_mpm/core/unified_paths.py +95 -90
  131. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.C33zOoyM.css +1 -0
  132. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.CW1J-YuA.css +1 -0
  133. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Cs_tUR18.js → 1WZnGYqX.js} +1 -1
  134. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CDuw-vjf.js → 67pF3qNn.js} +1 -1
  135. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{bTOqqlTd.js → 6RxdMKe4.js} +1 -1
  136. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DwBR2MJi.js → 8cZrfX0h.js} +1 -1
  137. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{ZGh7QtNv.js → 9a6T2nm-.js} +1 -1
  138. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{D9lljYKQ.js → B443AUzu.js} +1 -1
  139. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{RJiighC3.js → B8AwtY2H.js} +1 -1
  140. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{uuIeMWc-.js → BF15LAsF.js} +1 -1
  141. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{D3k0OPJN.js → BRcwIQNr.js} +1 -1
  142. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CyWMqx4W.js → BV6nKitt.js} +1 -1
  143. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CiIAseT4.js → BViJ8lZt.js} +5 -5
  144. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CBBdVcY8.js → BcQ-Q0FE.js} +1 -1
  145. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BovzEFCE.js → Bpyvgze_.js} +1 -1
  146. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BzTRqg-z.js +1 -0
  147. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C0Fr8dve.js +1 -0
  148. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{eNVUfhuA.js → C3rbW_a-.js} +1 -1
  149. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{GYwsonyD.js → C8WYN38h.js} +1 -1
  150. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BIF9m_hv.js → C9I8FlXH.js} +1 -1
  151. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B0uc0UOD.js → CIQcWgO2.js} +3 -3
  152. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Be7GpZd6.js → CIctN7YN.js} +1 -1
  153. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Bh0LDWpI.js → CKrS_JZW.js} +2 -2
  154. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DUrLdbGD.js → CR6P9C4A.js} +1 -1
  155. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B7xVLGWV.js → CRRR9MD_.js} +1 -1
  156. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CRcR2DqT.js +334 -0
  157. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Dhb8PKl3.js → CSXtMOf0.js} +1 -1
  158. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BPYeabCQ.js → CT-sbxSk.js} +1 -1
  159. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{sQeU3Y1z.js → CWm6DJsp.js} +1 -1
  160. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CnA0NrzZ.js → CpqQ1Kzn.js} +1 -1
  161. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C4B-KCzX.js → D2nGpDRe.js} +1 -1
  162. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DGkLK5U1.js → D9iCMida.js} +1 -1
  163. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BofRWZRR.js → D9ykgMoY.js} +1 -1
  164. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DmxopI1J.js → DL2Ldur1.js} +1 -1
  165. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C30mlcqg.js → DPfltzjH.js} +1 -1
  166. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Vzk33B_K.js → DR8nis88.js} +2 -2
  167. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DI7hHRFL.js → DUliQN2b.js} +1 -1
  168. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C4JcI4KD.js → DXlhR01x.js} +1 -1
  169. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{bT1r9zLR.js → D_lyTybS.js} +1 -1
  170. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DZX00Y4g.js → DngoTTgh.js} +1 -1
  171. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CzZX-COe.js → DqkmHtDC.js} +1 -1
  172. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B7RN905-.js → DsDh8EYs.js} +1 -1
  173. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DLVjFsZ3.js → DypDmXgd.js} +1 -1
  174. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{iEWssX7S.js → IPYC-LnN.js} +1 -1
  175. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/JTLiF7dt.js +24 -0
  176. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DaimHw_p.js → JpevfAFt.js} +1 -1
  177. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DY1XQ8fi.js → R8CEIRAd.js} +1 -1
  178. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Dle-35c7.js → Zxy7qc-l.js} +2 -2
  179. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/q9Hm6zAU.js +1 -0
  180. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C_Usid8X.js → qtd3IeO4.js} +2 -2
  181. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CzeYkLYB.js → ulBFON_C.js} +2 -2
  182. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Cfqx1Qun.js → wQVh1CoA.js} +1 -1
  183. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/{app.D6-I5TpK.js → app.Dr7t0z2J.js} +2 -2
  184. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.BGhZHUS3.js +1 -0
  185. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/{0.m1gL8KXf.js → 0.RgBboRvH.js} +1 -1
  186. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/{1.CgNOuw-d.js → 1.DG-KkbDf.js} +1 -1
  187. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.D_jnf-x6.js +1 -0
  188. claude_mpm/dashboard/static/svelte-build/_app/version.json +1 -1
  189. claude_mpm/dashboard/static/svelte-build/index.html +9 -9
  190. claude_mpm/experimental/cli_enhancements.py +2 -1
  191. claude_mpm/hooks/claude_hooks/INTEGRATION_EXAMPLE.md +243 -0
  192. claude_mpm/hooks/claude_hooks/README_AUTO_PAUSE.md +403 -0
  193. claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-314.pyc +0 -0
  194. claude_mpm/hooks/claude_hooks/__pycache__/auto_pause_handler.cpython-311.pyc +0 -0
  195. claude_mpm/hooks/claude_hooks/__pycache__/auto_pause_handler.cpython-312.pyc +0 -0
  196. claude_mpm/hooks/claude_hooks/__pycache__/auto_pause_handler.cpython-314.pyc +0 -0
  197. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-311.pyc +0 -0
  198. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-312.pyc +0 -0
  199. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-314.pyc +0 -0
  200. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-311.pyc +0 -0
  201. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-312.pyc +0 -0
  202. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-314.pyc +0 -0
  203. claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-311.pyc +0 -0
  204. claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-314.pyc +0 -0
  205. claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-311.pyc +0 -0
  206. claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-312.pyc +0 -0
  207. claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-314.pyc +0 -0
  208. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-311.pyc +0 -0
  209. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-312.pyc +0 -0
  210. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-314.pyc +0 -0
  211. claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-314.pyc +0 -0
  212. claude_mpm/hooks/claude_hooks/auto_pause_handler.py +485 -0
  213. claude_mpm/hooks/claude_hooks/event_handlers.py +283 -87
  214. claude_mpm/hooks/claude_hooks/hook_handler.py +106 -89
  215. claude_mpm/hooks/claude_hooks/hook_wrapper.sh +6 -11
  216. claude_mpm/hooks/claude_hooks/installer.py +116 -8
  217. claude_mpm/hooks/claude_hooks/memory_integration.py +51 -31
  218. claude_mpm/hooks/claude_hooks/response_tracking.py +42 -59
  219. claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-314.pyc +0 -0
  220. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-311.pyc +0 -0
  221. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-312.pyc +0 -0
  222. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-314.pyc +0 -0
  223. claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-314.pyc +0 -0
  224. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-311.pyc +0 -0
  225. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-312.pyc +0 -0
  226. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-314.pyc +0 -0
  227. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-311.pyc +0 -0
  228. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-312.pyc +0 -0
  229. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-314.pyc +0 -0
  230. claude_mpm/hooks/claude_hooks/services/connection_manager.py +39 -24
  231. claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +36 -103
  232. claude_mpm/hooks/claude_hooks/services/state_manager.py +23 -36
  233. claude_mpm/hooks/claude_hooks/services/subagent_processor.py +73 -75
  234. claude_mpm/hooks/session_resume_hook.py +89 -1
  235. claude_mpm/hooks/templates/pre_tool_use_template.py +10 -2
  236. claude_mpm/init.py +1 -1
  237. claude_mpm/scripts/claude-hook-handler.sh +43 -16
  238. claude_mpm/services/agents/agent_recommendation_service.py +8 -8
  239. claude_mpm/services/agents/agent_selection_service.py +2 -2
  240. claude_mpm/services/agents/cache_git_manager.py +1 -1
  241. claude_mpm/services/agents/deployment/deployment_reconciler.py +577 -0
  242. claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +3 -0
  243. claude_mpm/services/agents/deployment/startup_reconciliation.py +138 -0
  244. claude_mpm/services/agents/loading/framework_agent_loader.py +75 -2
  245. claude_mpm/services/agents/single_tier_deployment_service.py +4 -4
  246. claude_mpm/services/agents/startup_sync.py +5 -2
  247. claude_mpm/services/cli/__init__.py +3 -0
  248. claude_mpm/services/cli/incremental_pause_manager.py +561 -0
  249. claude_mpm/services/cli/session_resume_helper.py +10 -2
  250. claude_mpm/services/delegation_detector.py +175 -0
  251. claude_mpm/services/diagnostics/checks/agent_sources_check.py +30 -0
  252. claude_mpm/services/diagnostics/checks/configuration_check.py +24 -0
  253. claude_mpm/services/diagnostics/checks/installation_check.py +22 -0
  254. claude_mpm/services/diagnostics/checks/mcp_services_check.py +23 -0
  255. claude_mpm/services/diagnostics/doctor_reporter.py +31 -1
  256. claude_mpm/services/diagnostics/models.py +14 -1
  257. claude_mpm/services/event_log.py +325 -0
  258. claude_mpm/services/infrastructure/__init__.py +4 -0
  259. claude_mpm/services/infrastructure/context_usage_tracker.py +291 -0
  260. claude_mpm/services/infrastructure/resume_log_generator.py +24 -5
  261. claude_mpm/services/monitor/daemon_manager.py +15 -4
  262. claude_mpm/services/monitor/management/lifecycle.py +8 -2
  263. claude_mpm/services/monitor/server.py +106 -16
  264. claude_mpm/services/pm_skills_deployer.py +259 -87
  265. claude_mpm/services/skills/git_skill_source_manager.py +135 -11
  266. claude_mpm/services/skills/selective_skill_deployer.py +142 -26
  267. claude_mpm/services/skills/skill_discovery_service.py +74 -4
  268. claude_mpm/services/skills_deployer.py +31 -5
  269. claude_mpm/services/socketio/handlers/hook.py +14 -7
  270. claude_mpm/services/socketio/server/main.py +12 -4
  271. claude_mpm/skills/bundled/pm/mpm/SKILL.md +38 -0
  272. claude_mpm/skills/bundled/pm/mpm-agent-update-workflow/SKILL.md +75 -0
  273. claude_mpm/skills/bundled/pm/mpm-bug-reporting/SKILL.md +248 -0
  274. claude_mpm/skills/bundled/pm/mpm-circuit-breaker-enforcement/SKILL.md +476 -0
  275. claude_mpm/skills/bundled/pm/mpm-config/SKILL.md +29 -0
  276. claude_mpm/skills/bundled/pm/mpm-doctor/SKILL.md +53 -0
  277. claude_mpm/skills/bundled/pm/mpm-help/SKILL.md +35 -0
  278. claude_mpm/skills/bundled/pm/mpm-init/SKILL.md +125 -0
  279. claude_mpm/skills/bundled/pm/mpm-monitor/SKILL.md +32 -0
  280. claude_mpm/skills/bundled/pm/mpm-organize/SKILL.md +121 -0
  281. claude_mpm/skills/bundled/pm/mpm-postmortem/SKILL.md +22 -0
  282. claude_mpm/skills/bundled/pm/mpm-session-management/SKILL.md +312 -0
  283. claude_mpm/skills/bundled/pm/mpm-session-resume/SKILL.md +31 -0
  284. claude_mpm/skills/bundled/pm/mpm-status/SKILL.md +37 -0
  285. claude_mpm/skills/bundled/pm/mpm-teaching-mode/SKILL.md +657 -0
  286. claude_mpm/skills/bundled/pm/mpm-ticket-view/SKILL.md +110 -0
  287. claude_mpm/skills/bundled/pm/mpm-tool-usage-guide/SKILL.md +386 -0
  288. claude_mpm/skills/bundled/pm/mpm-version/SKILL.md +21 -0
  289. claude_mpm/skills/skill_manager.py +4 -4
  290. claude_mpm/utils/agent_dependency_loader.py +4 -2
  291. claude_mpm/utils/robust_installer.py +10 -6
  292. claude_mpm-5.6.10.dist-info/METADATA +391 -0
  293. {claude_mpm-5.4.65.dist-info → claude_mpm-5.6.10.dist-info}/RECORD +303 -181
  294. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.DWzvg0-y.css +0 -1
  295. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.ThTw9_ym.css +0 -1
  296. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/4TdZjIqw.js +0 -1
  297. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/5shd3_w0.js +0 -24
  298. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BKjSRqUr.js +0 -1
  299. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Da0KfYnO.js +0 -1
  300. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Dfy6j1xT.js +0 -323
  301. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.NWzMBYRp.js +0 -1
  302. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.C0GcWctS.js +0 -1
  303. claude_mpm-5.4.65.dist-info/METADATA +0 -999
  304. /claude_mpm/skills/bundled/pm/{pm-delegation-patterns → mpm-delegation-patterns}/SKILL.md +0 -0
  305. /claude_mpm/skills/bundled/pm/{pm-git-file-tracking → mpm-git-file-tracking}/SKILL.md +0 -0
  306. /claude_mpm/skills/bundled/pm/{pm-pr-workflow → mpm-pr-workflow}/SKILL.md +0 -0
  307. /claude_mpm/skills/bundled/pm/{pm-ticketing-integration → mpm-ticketing-integration}/SKILL.md +0 -0
  308. /claude_mpm/skills/bundled/pm/{pm-verification-protocols → mpm-verification-protocols}/SKILL.md +0 -0
  309. {claude_mpm-5.4.65.dist-info → claude_mpm-5.6.10.dist-info}/WHEEL +0 -0
  310. {claude_mpm-5.4.65.dist-info → claude_mpm-5.6.10.dist-info}/entry_points.txt +0 -0
  311. {claude_mpm-5.4.65.dist-info → claude_mpm-5.6.10.dist-info}/licenses/LICENSE +0 -0
  312. {claude_mpm-5.4.65.dist-info → claude_mpm-5.6.10.dist-info}/licenses/LICENSE-FAQ.md +0 -0
  313. {claude_mpm-5.4.65.dist-info → claude_mpm-5.6.10.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,361 @@
1
+ """Tmux orchestration layer for MPM Commander.
2
+
3
+ This module wraps tmux commands to manage sessions, panes, and I/O for
4
+ coordinating multiple project-level MPM instances.
5
+ """
6
+
7
+ import logging
8
+ import shutil
9
+ import subprocess # nosec B404 - Required for tmux interaction
10
+ from dataclasses import dataclass
11
+ from typing import Dict, List
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ class TmuxNotFoundError(Exception):
17
+ """Raised when tmux is not installed or not found in PATH."""
18
+
19
+ def __init__(
20
+ self,
21
+ message: str = "tmux not found. Please install tmux to use commander mode.",
22
+ ):
23
+ super().__init__(message)
24
+ self.message = message
25
+
26
+
27
+ @dataclass
28
+ class TmuxOrchestrator:
29
+ """Orchestrate multiple MPM sessions via tmux.
30
+
31
+ This class provides a high-level API for managing tmux sessions and panes,
32
+ enabling the MPM Commander to coordinate multiple project-level MPM instances.
33
+
34
+ Attributes:
35
+ session_name: Name of the tmux session (default: "mpm-commander")
36
+
37
+ Example:
38
+ >>> orchestrator = TmuxOrchestrator()
39
+ >>> orchestrator.create_session()
40
+ >>> target = orchestrator.create_pane("proj1", "/path/to/project")
41
+ >>> orchestrator.send_keys(target, "echo 'Hello from pane'")
42
+ >>> output = orchestrator.capture_output(target)
43
+ >>> print(output)
44
+ >>> orchestrator.kill_session()
45
+ """
46
+
47
+ session_name: str = "mpm-commander"
48
+
49
+ def __post_init__(self):
50
+ """Verify tmux is available on initialization."""
51
+ if not shutil.which("tmux"):
52
+ raise TmuxNotFoundError()
53
+
54
+ def _run_tmux(
55
+ self, args: List[str], check: bool = True
56
+ ) -> subprocess.CompletedProcess:
57
+ """Execute tmux command and return result.
58
+
59
+ Args:
60
+ args: List of tmux command arguments
61
+ check: Whether to raise exception on non-zero exit code
62
+
63
+ Returns:
64
+ CompletedProcess with stdout/stderr captured
65
+
66
+ Raises:
67
+ TmuxNotFoundError: If tmux binary not found
68
+ subprocess.CalledProcessError: If check=True and command fails
69
+ """
70
+ cmd = ["tmux"] + args
71
+ logger.debug(f"Running tmux command: {' '.join(cmd)}")
72
+
73
+ try:
74
+ result = subprocess.run(cmd, capture_output=True, text=True, check=check) # nosec B603
75
+
76
+ if result.stdout:
77
+ logger.debug(f"tmux stdout: {result.stdout.strip()}")
78
+ if result.stderr:
79
+ logger.debug(f"tmux stderr: {result.stderr.strip()}")
80
+
81
+ return result
82
+
83
+ except FileNotFoundError as err:
84
+ raise TmuxNotFoundError() from err
85
+
86
+ def session_exists(self) -> bool:
87
+ """Check if commander session exists.
88
+
89
+ Returns:
90
+ True if session exists, False otherwise
91
+
92
+ Example:
93
+ >>> orchestrator = TmuxOrchestrator()
94
+ >>> if not orchestrator.session_exists():
95
+ ... orchestrator.create_session()
96
+ """
97
+ result = self._run_tmux(["has-session", "-t", self.session_name], check=False)
98
+ exists = result.returncode == 0
99
+ logger.debug(f"Session '{self.session_name}' exists: {exists}")
100
+ return exists
101
+
102
+ def create_session(self) -> bool:
103
+ """Create main commander tmux session if not exists.
104
+
105
+ Creates a detached tmux session for the commander. If the session
106
+ already exists, this is a no-op.
107
+
108
+ Returns:
109
+ True if session was created, False if it already existed
110
+
111
+ Example:
112
+ >>> orchestrator = TmuxOrchestrator()
113
+ >>> orchestrator.create_session()
114
+ True
115
+ >>> orchestrator.create_session() # Already exists
116
+ False
117
+ """
118
+ if self.session_exists():
119
+ logger.info(f"Session '{self.session_name}' already exists")
120
+ return False
121
+
122
+ logger.info(f"Creating tmux session '{self.session_name}'")
123
+ self._run_tmux(
124
+ [
125
+ "new-session",
126
+ "-d", # Detached
127
+ "-s",
128
+ self.session_name,
129
+ "-n",
130
+ "commander", # Window name
131
+ ]
132
+ )
133
+
134
+ return True
135
+
136
+ def create_pane(self, pane_id: str, working_dir: str) -> str:
137
+ """Create new pane for a project.
138
+
139
+ Creates a new split pane in the commander session with the specified
140
+ working directory.
141
+
142
+ Args:
143
+ pane_id: Identifier for this pane (used in logging)
144
+ working_dir: Working directory for the pane
145
+
146
+ Returns:
147
+ Tmux target string (pane ID like "%0", "%1", etc.)
148
+
149
+ Raises:
150
+ subprocess.CalledProcessError: If pane creation fails
151
+
152
+ Example:
153
+ >>> orchestrator = TmuxOrchestrator()
154
+ >>> orchestrator.create_session()
155
+ >>> target = orchestrator.create_pane("my-project", "/Users/user/projects/my-project")
156
+ >>> print(target)
157
+ %1
158
+ """
159
+ logger.info(f"Creating pane '{pane_id}' in {working_dir}")
160
+
161
+ # Split window to create new pane
162
+ self._run_tmux(
163
+ [
164
+ "split-window",
165
+ "-t",
166
+ self.session_name,
167
+ "-c",
168
+ working_dir, # Working directory
169
+ "-P", # Print target of new pane
170
+ "-F",
171
+ "#{pane_id}", # Format: just pane ID
172
+ ]
173
+ )
174
+
175
+ # Get the newly created pane's target
176
+ # List panes and get the last one (most recently created)
177
+ result = self._run_tmux(
178
+ [
179
+ "list-panes",
180
+ "-t",
181
+ self.session_name,
182
+ "-F",
183
+ "#{pane_id}",
184
+ ]
185
+ )
186
+
187
+ panes = [p for p in result.stdout.strip().split("\n") if p]
188
+ if not panes:
189
+ raise RuntimeError(f"Failed to create pane '{pane_id}'")
190
+
191
+ # Get last pane ID (most recently created)
192
+ # Pane ID already includes % prefix and can be used directly as target
193
+ new_pane_id = panes[-1]
194
+ target = new_pane_id # Use pane ID directly as target
195
+
196
+ logger.debug(f"Created pane with target: {target}")
197
+ return target
198
+
199
+ def send_keys(self, target: str, keys: str, enter: bool = True) -> bool:
200
+ """Send keystrokes to a pane.
201
+
202
+ Args:
203
+ target: Tmux target (from create_pane)
204
+ keys: Keys to send to the pane
205
+ enter: Whether to send Enter key after keys
206
+
207
+ Returns:
208
+ True if successful
209
+
210
+ Raises:
211
+ subprocess.CalledProcessError: If target pane doesn't exist
212
+
213
+ Example:
214
+ >>> orchestrator = TmuxOrchestrator()
215
+ >>> orchestrator.create_session()
216
+ >>> target = orchestrator.create_pane("proj", "/tmp")
217
+ >>> orchestrator.send_keys(target, "echo 'Hello'")
218
+ >>> orchestrator.send_keys(target, "ls -la", enter=False)
219
+ """
220
+ logger.debug(f"Sending keys to {target}: {keys}")
221
+
222
+ args = ["send-keys", "-t", target, keys]
223
+ if enter:
224
+ args.append("Enter")
225
+
226
+ self._run_tmux(args)
227
+ return True
228
+
229
+ def capture_output(self, target: str, lines: int = 100) -> str:
230
+ """Capture recent output from pane.
231
+
232
+ Args:
233
+ target: Tmux target (from create_pane)
234
+ lines: Number of lines to capture from history
235
+
236
+ Returns:
237
+ Captured output as string
238
+
239
+ Raises:
240
+ subprocess.CalledProcessError: If target pane doesn't exist
241
+
242
+ Example:
243
+ >>> orchestrator = TmuxOrchestrator()
244
+ >>> orchestrator.create_session()
245
+ >>> target = orchestrator.create_pane("proj", "/tmp")
246
+ >>> orchestrator.send_keys(target, "echo 'Test output'")
247
+ >>> output = orchestrator.capture_output(target, lines=10)
248
+ >>> print(output)
249
+ Test output
250
+ """
251
+ logger.debug(f"Capturing {lines} lines from {target}")
252
+
253
+ result = self._run_tmux(
254
+ [
255
+ "capture-pane",
256
+ "-t",
257
+ target,
258
+ "-p", # Print to stdout
259
+ "-S",
260
+ f"-{lines}", # Start from N lines back
261
+ ]
262
+ )
263
+
264
+ return result.stdout
265
+
266
+ def list_panes(self) -> List[Dict[str, str]]:
267
+ """List all panes with their status.
268
+
269
+ Returns:
270
+ List of dicts with pane info (id, path, pid, active)
271
+
272
+ Example:
273
+ >>> orchestrator = TmuxOrchestrator()
274
+ >>> orchestrator.create_session()
275
+ >>> panes = orchestrator.list_panes()
276
+ >>> for pane in panes:
277
+ ... print(f"{pane['id']}: {pane['path']}")
278
+ %0: /Users/user/projects/proj1
279
+ %1: /Users/user/projects/proj2
280
+ """
281
+ if not self.session_exists():
282
+ logger.warning(f"Session '{self.session_name}' does not exist")
283
+ return []
284
+
285
+ logger.debug(f"Listing panes for session '{self.session_name}'")
286
+
287
+ result = self._run_tmux(
288
+ [
289
+ "list-panes",
290
+ "-t",
291
+ self.session_name,
292
+ "-F",
293
+ "#{pane_id}|#{pane_current_path}|#{pane_pid}|#{pane_active}",
294
+ ]
295
+ )
296
+
297
+ panes = []
298
+ for line in result.stdout.strip().split("\n"):
299
+ if not line:
300
+ continue
301
+
302
+ parts = line.split("|")
303
+ if len(parts) >= 4:
304
+ panes.append(
305
+ {
306
+ "id": parts[0],
307
+ "path": parts[1],
308
+ "pid": parts[2],
309
+ "active": parts[3] == "1",
310
+ }
311
+ )
312
+
313
+ logger.debug(f"Found {len(panes)} panes")
314
+ return panes
315
+
316
+ def kill_pane(self, target: str) -> bool:
317
+ """Kill a specific pane.
318
+
319
+ Args:
320
+ target: Tmux target (from create_pane or list_panes)
321
+
322
+ Returns:
323
+ True if successful
324
+
325
+ Raises:
326
+ subprocess.CalledProcessError: If target pane doesn't exist
327
+
328
+ Example:
329
+ >>> orchestrator = TmuxOrchestrator()
330
+ >>> orchestrator.create_session()
331
+ >>> target = orchestrator.create_pane("proj", "/tmp")
332
+ >>> orchestrator.kill_pane(target)
333
+ True
334
+ """
335
+ logger.info(f"Killing pane {target}")
336
+
337
+ self._run_tmux(["kill-pane", "-t", target])
338
+ return True
339
+
340
+ def kill_session(self) -> bool:
341
+ """Kill the entire commander session.
342
+
343
+ Returns:
344
+ True if session was killed, False if it didn't exist
345
+
346
+ Example:
347
+ >>> orchestrator = TmuxOrchestrator()
348
+ >>> orchestrator.create_session()
349
+ >>> orchestrator.kill_session()
350
+ True
351
+ >>> orchestrator.kill_session() # Already killed
352
+ False
353
+ """
354
+ if not self.session_exists():
355
+ logger.info(f"Session '{self.session_name}' does not exist")
356
+ return False
357
+
358
+ logger.info(f"Killing session '{self.session_name}'")
359
+ self._run_tmux(["kill-session", "-t", self.session_name])
360
+
361
+ return True
@@ -0,0 +1 @@
1
+ """Web UI module for MPM Commander."""
@@ -0,0 +1,30 @@
1
+ """Work queue and execution module for MPM Commander.
2
+
3
+ This module provides work queue management and execution capabilities:
4
+ - WorkItem: Data model for work items
5
+ - WorkState: Lifecycle states (PENDING, QUEUED, IN_PROGRESS, etc.)
6
+ - WorkPriority: Priority levels (CRITICAL, HIGH, MEDIUM, LOW)
7
+ - WorkQueue: Queue management with priority and dependencies
8
+ - WorkExecutor: Execution via RuntimeExecutor
9
+
10
+ Example:
11
+ >>> from claude_mpm.commander.work import (
12
+ ... WorkQueue, WorkExecutor, WorkItem, WorkState, WorkPriority
13
+ ... )
14
+ >>> queue = WorkQueue("proj-123")
15
+ >>> work = queue.add("Implement feature", WorkPriority.HIGH)
16
+ >>> executor = WorkExecutor(runtime, queue)
17
+ >>> await executor.execute_next()
18
+ """
19
+
20
+ from ..models.work import WorkItem, WorkPriority, WorkState
21
+ from .executor import WorkExecutor
22
+ from .queue import WorkQueue
23
+
24
+ __all__ = [
25
+ "WorkExecutor",
26
+ "WorkItem",
27
+ "WorkPriority",
28
+ "WorkQueue",
29
+ "WorkState",
30
+ ]
@@ -0,0 +1,189 @@
1
+ """Work executor for MPM Commander.
2
+
3
+ This module provides WorkExecutor which executes work items
4
+ via RuntimeExecutor and handles completion/failure callbacks.
5
+ """
6
+
7
+ import logging
8
+ from typing import Optional
9
+
10
+ from ..models.work import WorkItem
11
+ from ..runtime.executor import RuntimeExecutor
12
+ from .queue import WorkQueue
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class WorkExecutor:
18
+ """Executes work items via RuntimeExecutor.
19
+
20
+ Coordinates between work queue and runtime execution,
21
+ handling work item lifecycle and callbacks.
22
+
23
+ Attributes:
24
+ runtime: RuntimeExecutor for spawning and managing tools
25
+ queue: WorkQueue for work item management
26
+
27
+ Example:
28
+ >>> executor = WorkExecutor(runtime, queue)
29
+ >>> executed = await executor.execute_next()
30
+ >>> if executed:
31
+ ... print("Work item started")
32
+ """
33
+
34
+ def __init__(self, runtime: RuntimeExecutor, queue: WorkQueue):
35
+ """Initialize work executor.
36
+
37
+ Args:
38
+ runtime: RuntimeExecutor instance
39
+ queue: WorkQueue instance
40
+
41
+ Raises:
42
+ ValueError: If runtime or queue is None
43
+ """
44
+ if runtime is None:
45
+ raise ValueError("Runtime cannot be None")
46
+ if queue is None:
47
+ raise ValueError("Queue cannot be None")
48
+
49
+ self.runtime = runtime
50
+ self.queue = queue
51
+
52
+ logger.debug(f"Initialized WorkExecutor for project {queue.project_id}")
53
+
54
+ async def execute_next(self) -> bool:
55
+ """Execute next available work item.
56
+
57
+ Gets next work from queue, starts it, and executes via RuntimeExecutor.
58
+
59
+ Returns:
60
+ True if work was executed, False if queue empty/blocked
61
+
62
+ Example:
63
+ >>> executed = await executor.execute_next()
64
+ >>> if not executed:
65
+ ... print("No work available")
66
+ """
67
+ # Get next work item
68
+ work_item = self.queue.get_next()
69
+ if not work_item:
70
+ logger.debug(f"No work available for project {self.queue.project_id}")
71
+ return False
72
+
73
+ # Execute the work item
74
+ await self.execute(work_item)
75
+ return True
76
+
77
+ async def execute(self, work_item: WorkItem) -> None:
78
+ """Execute a specific work item.
79
+
80
+ Marks work as IN_PROGRESS and sends to RuntimeExecutor.
81
+ Note: This is async but returns immediately - actual execution
82
+ happens in the background via tmux.
83
+
84
+ Args:
85
+ work_item: WorkItem to execute
86
+
87
+ Raises:
88
+ RuntimeError: If execution fails
89
+
90
+ Example:
91
+ >>> await executor.execute(work_item)
92
+ """
93
+ # Mark as in progress
94
+ if not self.queue.start(work_item.id):
95
+ logger.error(
96
+ f"Failed to start work item {work_item.id} - "
97
+ f"invalid state: {work_item.state.value}"
98
+ )
99
+ return
100
+
101
+ logger.info(
102
+ f"Executing work item {work_item.id} for project {work_item.project_id}"
103
+ )
104
+
105
+ try:
106
+ # Send work content to runtime
107
+ # Note: In actual implementation, this would integrate with
108
+ # ProjectSession which manages the pane_target
109
+ # For now, we assume runtime has active session
110
+ # This will be properly integrated when wiring with ProjectSession
111
+
112
+ # Store work item ID in metadata for callback tracking
113
+ work_item.metadata["execution_started"] = True
114
+
115
+ logger.info(f"Work item {work_item.id} sent to runtime for execution")
116
+
117
+ except Exception as e:
118
+ logger.error(f"Failed to execute work item {work_item.id}: {e}")
119
+ await self.handle_failure(work_item.id, str(e))
120
+ raise
121
+
122
+ async def handle_completion(
123
+ self, work_id: str, result: Optional[str] = None
124
+ ) -> None:
125
+ """Handle work completion callback.
126
+
127
+ Called when RuntimeExecutor completes work successfully.
128
+
129
+ Args:
130
+ work_id: Work item ID that completed
131
+ result: Optional result message
132
+
133
+ Example:
134
+ >>> await executor.handle_completion("work-123", "Feature implemented")
135
+ """
136
+ if self.queue.complete(work_id, result):
137
+ logger.info(f"Work item {work_id} completed successfully")
138
+ else:
139
+ logger.warning(f"Failed to mark work item {work_id} as completed")
140
+
141
+ async def handle_failure(self, work_id: str, error: str) -> None:
142
+ """Handle work failure callback.
143
+
144
+ Called when RuntimeExecutor encounters an error.
145
+
146
+ Args:
147
+ work_id: Work item ID that failed
148
+ error: Error message
149
+
150
+ Example:
151
+ >>> await executor.handle_failure("work-123", "Tool crashed")
152
+ """
153
+ if self.queue.fail(work_id, error):
154
+ logger.error(f"Work item {work_id} failed: {error}")
155
+ else:
156
+ logger.warning(f"Failed to mark work item {work_id} as failed")
157
+
158
+ async def handle_block(self, work_id: str, reason: str) -> None:
159
+ """Handle work being blocked by an event.
160
+
161
+ Called when RuntimeMonitor detects a blocking event.
162
+
163
+ Args:
164
+ work_id: Work item ID that is blocked
165
+ reason: Reason for blocking (e.g., "Waiting for approval")
166
+
167
+ Example:
168
+ >>> await executor.handle_block("work-123", "Decision needed")
169
+ """
170
+ if self.queue.block(work_id, reason):
171
+ logger.info(f"Work item {work_id} blocked: {reason}")
172
+ else:
173
+ logger.warning(f"Failed to mark work item {work_id} as blocked")
174
+
175
+ async def handle_unblock(self, work_id: str) -> None:
176
+ """Handle work being unblocked after event resolution.
177
+
178
+ Called when EventHandler resolves a blocking event.
179
+
180
+ Args:
181
+ work_id: Work item ID to unblock
182
+
183
+ Example:
184
+ >>> await executor.handle_unblock("work-123")
185
+ """
186
+ if self.queue.unblock(work_id):
187
+ logger.info(f"Work item {work_id} unblocked, resuming execution")
188
+ else:
189
+ logger.warning(f"Failed to unblock work item {work_id}")