claude-mpm 5.4.85__py3-none-any.whl → 5.6.76__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (322) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/CLAUDE_MPM_OUTPUT_STYLE.md +8 -5
  3. claude_mpm/agents/{CLAUDE_MPM_FOUNDERS_OUTPUT_STYLE.md → CLAUDE_MPM_RESEARCH_OUTPUT_STYLE.md} +14 -6
  4. claude_mpm/agents/PM_INSTRUCTIONS.md +109 -706
  5. claude_mpm/agents/WORKFLOW.md +2 -0
  6. claude_mpm/agents/templates/circuit-breakers.md +26 -17
  7. claude_mpm/auth/__init__.py +35 -0
  8. claude_mpm/auth/callback_server.py +328 -0
  9. claude_mpm/auth/models.py +104 -0
  10. claude_mpm/auth/oauth_manager.py +266 -0
  11. claude_mpm/auth/providers/__init__.py +12 -0
  12. claude_mpm/auth/providers/base.py +165 -0
  13. claude_mpm/auth/providers/google.py +261 -0
  14. claude_mpm/auth/token_storage.py +252 -0
  15. claude_mpm/cli/commands/autotodos.py +566 -0
  16. claude_mpm/cli/commands/commander.py +216 -0
  17. claude_mpm/cli/commands/hook_errors.py +60 -60
  18. claude_mpm/cli/commands/mcp.py +29 -17
  19. claude_mpm/cli/commands/mcp_command_router.py +39 -0
  20. claude_mpm/cli/commands/mcp_service_commands.py +304 -0
  21. claude_mpm/cli/commands/monitor.py +2 -2
  22. claude_mpm/cli/commands/mpm_init/core.py +2 -2
  23. claude_mpm/cli/commands/oauth.py +481 -0
  24. claude_mpm/cli/commands/run.py +35 -3
  25. claude_mpm/cli/commands/skill_source.py +51 -2
  26. claude_mpm/cli/commands/skills.py +5 -3
  27. claude_mpm/cli/executor.py +128 -16
  28. claude_mpm/cli/helpers.py +1 -1
  29. claude_mpm/cli/parsers/base_parser.py +84 -1
  30. claude_mpm/cli/parsers/commander_parser.py +116 -0
  31. claude_mpm/cli/parsers/mcp_parser.py +79 -0
  32. claude_mpm/cli/parsers/oauth_parser.py +165 -0
  33. claude_mpm/cli/parsers/run_parser.py +10 -0
  34. claude_mpm/cli/parsers/skill_source_parser.py +4 -0
  35. claude_mpm/cli/parsers/skills_parser.py +5 -0
  36. claude_mpm/cli/startup.py +345 -40
  37. claude_mpm/cli/startup_display.py +76 -7
  38. claude_mpm/cli/startup_logging.py +2 -2
  39. claude_mpm/cli/startup_migrations.py +236 -0
  40. claude_mpm/cli/utils.py +7 -3
  41. claude_mpm/commander/__init__.py +78 -0
  42. claude_mpm/commander/adapters/__init__.py +60 -0
  43. claude_mpm/commander/adapters/auggie.py +260 -0
  44. claude_mpm/commander/adapters/base.py +288 -0
  45. claude_mpm/commander/adapters/claude_code.py +392 -0
  46. claude_mpm/commander/adapters/codex.py +237 -0
  47. claude_mpm/commander/adapters/communication.py +366 -0
  48. claude_mpm/commander/adapters/example_usage.py +310 -0
  49. claude_mpm/commander/adapters/mpm.py +389 -0
  50. claude_mpm/commander/adapters/registry.py +204 -0
  51. claude_mpm/commander/api/__init__.py +16 -0
  52. claude_mpm/commander/api/app.py +121 -0
  53. claude_mpm/commander/api/errors.py +133 -0
  54. claude_mpm/commander/api/routes/__init__.py +8 -0
  55. claude_mpm/commander/api/routes/events.py +184 -0
  56. claude_mpm/commander/api/routes/inbox.py +171 -0
  57. claude_mpm/commander/api/routes/messages.py +148 -0
  58. claude_mpm/commander/api/routes/projects.py +271 -0
  59. claude_mpm/commander/api/routes/sessions.py +226 -0
  60. claude_mpm/commander/api/routes/work.py +296 -0
  61. claude_mpm/commander/api/schemas.py +186 -0
  62. claude_mpm/commander/chat/__init__.py +7 -0
  63. claude_mpm/commander/chat/cli.py +149 -0
  64. claude_mpm/commander/chat/commands.py +124 -0
  65. claude_mpm/commander/chat/repl.py +1957 -0
  66. claude_mpm/commander/config.py +51 -0
  67. claude_mpm/commander/config_loader.py +115 -0
  68. claude_mpm/commander/core/__init__.py +10 -0
  69. claude_mpm/commander/core/block_manager.py +325 -0
  70. claude_mpm/commander/core/response_manager.py +323 -0
  71. claude_mpm/commander/daemon.py +603 -0
  72. claude_mpm/commander/env_loader.py +59 -0
  73. claude_mpm/commander/events/__init__.py +26 -0
  74. claude_mpm/commander/events/manager.py +392 -0
  75. claude_mpm/commander/frameworks/__init__.py +12 -0
  76. claude_mpm/commander/frameworks/base.py +233 -0
  77. claude_mpm/commander/frameworks/claude_code.py +58 -0
  78. claude_mpm/commander/frameworks/mpm.py +57 -0
  79. claude_mpm/commander/git/__init__.py +5 -0
  80. claude_mpm/commander/git/worktree_manager.py +212 -0
  81. claude_mpm/commander/inbox/__init__.py +16 -0
  82. claude_mpm/commander/inbox/dedup.py +128 -0
  83. claude_mpm/commander/inbox/inbox.py +224 -0
  84. claude_mpm/commander/inbox/models.py +70 -0
  85. claude_mpm/commander/instance_manager.py +868 -0
  86. claude_mpm/commander/llm/__init__.py +6 -0
  87. claude_mpm/commander/llm/openrouter_client.py +167 -0
  88. claude_mpm/commander/llm/summarizer.py +70 -0
  89. claude_mpm/commander/memory/__init__.py +45 -0
  90. claude_mpm/commander/memory/compression.py +347 -0
  91. claude_mpm/commander/memory/embeddings.py +230 -0
  92. claude_mpm/commander/memory/entities.py +310 -0
  93. claude_mpm/commander/memory/example_usage.py +290 -0
  94. claude_mpm/commander/memory/integration.py +325 -0
  95. claude_mpm/commander/memory/search.py +381 -0
  96. claude_mpm/commander/memory/store.py +657 -0
  97. claude_mpm/commander/models/__init__.py +18 -0
  98. claude_mpm/commander/models/events.py +127 -0
  99. claude_mpm/commander/models/project.py +162 -0
  100. claude_mpm/commander/models/work.py +214 -0
  101. claude_mpm/commander/parsing/__init__.py +20 -0
  102. claude_mpm/commander/parsing/extractor.py +132 -0
  103. claude_mpm/commander/parsing/output_parser.py +270 -0
  104. claude_mpm/commander/parsing/patterns.py +100 -0
  105. claude_mpm/commander/persistence/__init__.py +11 -0
  106. claude_mpm/commander/persistence/event_store.py +274 -0
  107. claude_mpm/commander/persistence/state_store.py +403 -0
  108. claude_mpm/commander/persistence/work_store.py +164 -0
  109. claude_mpm/commander/polling/__init__.py +13 -0
  110. claude_mpm/commander/polling/event_detector.py +104 -0
  111. claude_mpm/commander/polling/output_buffer.py +49 -0
  112. claude_mpm/commander/polling/output_poller.py +153 -0
  113. claude_mpm/commander/project_session.py +268 -0
  114. claude_mpm/commander/proxy/__init__.py +12 -0
  115. claude_mpm/commander/proxy/formatter.py +89 -0
  116. claude_mpm/commander/proxy/output_handler.py +191 -0
  117. claude_mpm/commander/proxy/relay.py +155 -0
  118. claude_mpm/commander/registry.py +410 -0
  119. claude_mpm/commander/runtime/__init__.py +10 -0
  120. claude_mpm/commander/runtime/executor.py +191 -0
  121. claude_mpm/commander/runtime/monitor.py +346 -0
  122. claude_mpm/commander/session/__init__.py +6 -0
  123. claude_mpm/commander/session/context.py +81 -0
  124. claude_mpm/commander/session/manager.py +59 -0
  125. claude_mpm/commander/tmux_orchestrator.py +362 -0
  126. claude_mpm/commander/web/__init__.py +1 -0
  127. claude_mpm/commander/work/__init__.py +30 -0
  128. claude_mpm/commander/work/executor.py +207 -0
  129. claude_mpm/commander/work/queue.py +405 -0
  130. claude_mpm/commander/workflow/__init__.py +27 -0
  131. claude_mpm/commander/workflow/event_handler.py +241 -0
  132. claude_mpm/commander/workflow/notifier.py +146 -0
  133. claude_mpm/commands/mpm-config.md +8 -0
  134. claude_mpm/commands/mpm-doctor.md +8 -0
  135. claude_mpm/commands/mpm-help.md +8 -0
  136. claude_mpm/commands/mpm-init.md +8 -0
  137. claude_mpm/commands/mpm-monitor.md +8 -0
  138. claude_mpm/commands/mpm-organize.md +8 -0
  139. claude_mpm/commands/mpm-postmortem.md +8 -0
  140. claude_mpm/commands/mpm-session-resume.md +9 -1
  141. claude_mpm/commands/mpm-status.md +8 -0
  142. claude_mpm/commands/mpm-ticket-view.md +8 -0
  143. claude_mpm/commands/mpm-version.md +8 -0
  144. claude_mpm/commands/mpm.md +8 -0
  145. claude_mpm/config/agent_presets.py +8 -7
  146. claude_mpm/config/skill_sources.py +16 -0
  147. claude_mpm/constants.py +5 -0
  148. claude_mpm/core/claude_runner.py +152 -0
  149. claude_mpm/core/config.py +35 -22
  150. claude_mpm/core/config_constants.py +74 -9
  151. claude_mpm/core/constants.py +56 -12
  152. claude_mpm/core/hook_manager.py +53 -4
  153. claude_mpm/core/interactive_session.py +5 -4
  154. claude_mpm/core/logger.py +26 -9
  155. claude_mpm/core/logging_utils.py +39 -13
  156. claude_mpm/core/network_config.py +148 -0
  157. claude_mpm/core/oneshot_session.py +7 -6
  158. claude_mpm/core/output_style_manager.py +52 -12
  159. claude_mpm/core/socketio_pool.py +47 -15
  160. claude_mpm/core/unified_config.py +10 -6
  161. claude_mpm/core/unified_paths.py +68 -80
  162. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.C33zOoyM.css +1 -0
  163. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.CW1J-YuA.css +1 -0
  164. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Cs_tUR18.js → 1WZnGYqX.js} +1 -1
  165. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CDuw-vjf.js → 67pF3qNn.js} +1 -1
  166. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{bTOqqlTd.js → 6RxdMKe4.js} +1 -1
  167. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DwBR2MJi.js → 8cZrfX0h.js} +1 -1
  168. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{ZGh7QtNv.js → 9a6T2nm-.js} +1 -1
  169. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{D9lljYKQ.js → B443AUzu.js} +1 -1
  170. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{RJiighC3.js → B8AwtY2H.js} +1 -1
  171. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{uuIeMWc-.js → BF15LAsF.js} +1 -1
  172. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{D3k0OPJN.js → BRcwIQNr.js} +1 -1
  173. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CyWMqx4W.js → BV6nKitt.js} +1 -1
  174. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CiIAseT4.js → BViJ8lZt.js} +5 -5
  175. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CBBdVcY8.js → BcQ-Q0FE.js} +1 -1
  176. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BovzEFCE.js → Bpyvgze_.js} +1 -1
  177. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BzTRqg-z.js +1 -0
  178. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C0Fr8dve.js +1 -0
  179. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{eNVUfhuA.js → C3rbW_a-.js} +1 -1
  180. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{GYwsonyD.js → C8WYN38h.js} +1 -1
  181. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BIF9m_hv.js → C9I8FlXH.js} +1 -1
  182. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B0uc0UOD.js → CIQcWgO2.js} +3 -3
  183. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Be7GpZd6.js → CIctN7YN.js} +1 -1
  184. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Bh0LDWpI.js → CKrS_JZW.js} +2 -2
  185. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DUrLdbGD.js → CR6P9C4A.js} +1 -1
  186. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B7xVLGWV.js → CRRR9MD_.js} +1 -1
  187. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CRcR2DqT.js +334 -0
  188. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Dhb8PKl3.js → CSXtMOf0.js} +1 -1
  189. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BPYeabCQ.js → CT-sbxSk.js} +1 -1
  190. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{sQeU3Y1z.js → CWm6DJsp.js} +1 -1
  191. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CnA0NrzZ.js → CpqQ1Kzn.js} +1 -1
  192. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C4B-KCzX.js → D2nGpDRe.js} +1 -1
  193. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DGkLK5U1.js → D9iCMida.js} +1 -1
  194. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BofRWZRR.js → D9ykgMoY.js} +1 -1
  195. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DmxopI1J.js → DL2Ldur1.js} +1 -1
  196. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C30mlcqg.js → DPfltzjH.js} +1 -1
  197. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Vzk33B_K.js → DR8nis88.js} +2 -2
  198. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DI7hHRFL.js → DUliQN2b.js} +1 -1
  199. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C4JcI4KD.js → DXlhR01x.js} +1 -1
  200. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{bT1r9zLR.js → D_lyTybS.js} +1 -1
  201. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DZX00Y4g.js → DngoTTgh.js} +1 -1
  202. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CzZX-COe.js → DqkmHtDC.js} +1 -1
  203. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B7RN905-.js → DsDh8EYs.js} +1 -1
  204. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DLVjFsZ3.js → DypDmXgd.js} +1 -1
  205. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{iEWssX7S.js → IPYC-LnN.js} +1 -1
  206. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/JTLiF7dt.js +24 -0
  207. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DaimHw_p.js → JpevfAFt.js} +1 -1
  208. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DY1XQ8fi.js → R8CEIRAd.js} +1 -1
  209. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Dle-35c7.js → Zxy7qc-l.js} +2 -2
  210. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/q9Hm6zAU.js +1 -0
  211. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C_Usid8X.js → qtd3IeO4.js} +2 -2
  212. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CzeYkLYB.js → ulBFON_C.js} +2 -2
  213. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Cfqx1Qun.js → wQVh1CoA.js} +1 -1
  214. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/{app.D6-I5TpK.js → app.Dr7t0z2J.js} +2 -2
  215. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.BGhZHUS3.js +1 -0
  216. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/{0.m1gL8KXf.js → 0.RgBboRvH.js} +1 -1
  217. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/{1.CgNOuw-d.js → 1.DG-KkbDf.js} +1 -1
  218. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.D_jnf-x6.js +1 -0
  219. claude_mpm/dashboard/static/svelte-build/_app/version.json +1 -1
  220. claude_mpm/dashboard/static/svelte-build/index.html +9 -9
  221. claude_mpm/experimental/cli_enhancements.py +2 -1
  222. claude_mpm/hooks/claude_hooks/INTEGRATION_EXAMPLE.md +243 -0
  223. claude_mpm/hooks/claude_hooks/README_AUTO_PAUSE.md +403 -0
  224. claude_mpm/hooks/claude_hooks/auto_pause_handler.py +485 -0
  225. claude_mpm/hooks/claude_hooks/event_handlers.py +466 -136
  226. claude_mpm/hooks/claude_hooks/hook_handler.py +204 -104
  227. claude_mpm/hooks/claude_hooks/hook_wrapper.sh +6 -11
  228. claude_mpm/hooks/claude_hooks/installer.py +291 -59
  229. claude_mpm/hooks/claude_hooks/memory_integration.py +52 -32
  230. claude_mpm/hooks/claude_hooks/response_tracking.py +43 -60
  231. claude_mpm/hooks/claude_hooks/services/__init__.py +21 -0
  232. claude_mpm/hooks/claude_hooks/services/connection_manager.py +41 -26
  233. claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +38 -105
  234. claude_mpm/hooks/claude_hooks/services/container.py +326 -0
  235. claude_mpm/hooks/claude_hooks/services/protocols.py +328 -0
  236. claude_mpm/hooks/claude_hooks/services/state_manager.py +25 -38
  237. claude_mpm/hooks/claude_hooks/services/subagent_processor.py +75 -77
  238. claude_mpm/hooks/session_resume_hook.py +89 -1
  239. claude_mpm/hooks/templates/pre_tool_use_simple.py +6 -6
  240. claude_mpm/hooks/templates/pre_tool_use_template.py +16 -8
  241. claude_mpm/init.py +22 -15
  242. claude_mpm/mcp/__init__.py +9 -0
  243. claude_mpm/mcp/google_workspace_server.py +610 -0
  244. claude_mpm/scripts/claude-hook-handler.sh +46 -19
  245. claude_mpm/services/agents/agent_recommendation_service.py +8 -8
  246. claude_mpm/services/agents/agent_selection_service.py +2 -2
  247. claude_mpm/services/agents/cache_git_manager.py +1 -1
  248. claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +3 -0
  249. claude_mpm/services/agents/loading/framework_agent_loader.py +75 -2
  250. claude_mpm/services/agents/single_tier_deployment_service.py +4 -4
  251. claude_mpm/services/cli/__init__.py +3 -0
  252. claude_mpm/services/cli/incremental_pause_manager.py +561 -0
  253. claude_mpm/services/cli/session_resume_helper.py +10 -2
  254. claude_mpm/services/command_deployment_service.py +44 -26
  255. claude_mpm/services/delegation_detector.py +175 -0
  256. claude_mpm/services/diagnostics/checks/agent_sources_check.py +30 -0
  257. claude_mpm/services/diagnostics/checks/configuration_check.py +24 -0
  258. claude_mpm/services/diagnostics/checks/installation_check.py +22 -0
  259. claude_mpm/services/diagnostics/checks/mcp_services_check.py +23 -0
  260. claude_mpm/services/diagnostics/doctor_reporter.py +31 -1
  261. claude_mpm/services/diagnostics/models.py +14 -1
  262. claude_mpm/services/event_log.py +325 -0
  263. claude_mpm/services/hook_installer_service.py +77 -8
  264. claude_mpm/services/infrastructure/__init__.py +4 -0
  265. claude_mpm/services/infrastructure/context_usage_tracker.py +291 -0
  266. claude_mpm/services/infrastructure/resume_log_generator.py +24 -5
  267. claude_mpm/services/mcp_config_manager.py +99 -19
  268. claude_mpm/services/mcp_service_registry.py +294 -0
  269. claude_mpm/services/monitor/daemon_manager.py +15 -4
  270. claude_mpm/services/monitor/management/lifecycle.py +8 -2
  271. claude_mpm/services/monitor/server.py +111 -16
  272. claude_mpm/services/pm_skills_deployer.py +261 -87
  273. claude_mpm/services/skills/git_skill_source_manager.py +130 -10
  274. claude_mpm/services/skills/selective_skill_deployer.py +142 -16
  275. claude_mpm/services/skills/skill_discovery_service.py +74 -4
  276. claude_mpm/services/skills_deployer.py +31 -5
  277. claude_mpm/services/socketio/handlers/hook.py +14 -7
  278. claude_mpm/services/socketio/server/main.py +12 -4
  279. claude_mpm/skills/__init__.py +2 -1
  280. claude_mpm/skills/bundled/pm/mpm/SKILL.md +38 -0
  281. claude_mpm/skills/bundled/pm/mpm-agent-update-workflow/SKILL.md +75 -0
  282. claude_mpm/skills/bundled/pm/mpm-circuit-breaker-enforcement/SKILL.md +476 -0
  283. claude_mpm/skills/bundled/pm/mpm-config/SKILL.md +29 -0
  284. claude_mpm/skills/bundled/pm/mpm-doctor/SKILL.md +53 -0
  285. claude_mpm/skills/bundled/pm/mpm-help/SKILL.md +35 -0
  286. claude_mpm/skills/bundled/pm/mpm-init/SKILL.md +125 -0
  287. claude_mpm/skills/bundled/pm/mpm-monitor/SKILL.md +32 -0
  288. claude_mpm/skills/bundled/pm/mpm-organize/SKILL.md +121 -0
  289. claude_mpm/skills/bundled/pm/mpm-postmortem/SKILL.md +22 -0
  290. claude_mpm/skills/bundled/pm/mpm-session-management/SKILL.md +312 -0
  291. claude_mpm/skills/bundled/pm/mpm-session-pause/SKILL.md +170 -0
  292. claude_mpm/skills/bundled/pm/mpm-session-resume/SKILL.md +31 -0
  293. claude_mpm/skills/bundled/pm/mpm-status/SKILL.md +37 -0
  294. claude_mpm/skills/bundled/pm/{pm-teaching-mode → mpm-teaching-mode}/SKILL.md +2 -2
  295. claude_mpm/skills/bundled/pm/mpm-ticket-view/SKILL.md +110 -0
  296. claude_mpm/skills/bundled/pm/mpm-tool-usage-guide/SKILL.md +386 -0
  297. claude_mpm/skills/bundled/pm/mpm-version/SKILL.md +21 -0
  298. claude_mpm/skills/registry.py +295 -90
  299. claude_mpm/skills/skill_manager.py +4 -4
  300. claude_mpm-5.6.76.dist-info/METADATA +416 -0
  301. {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.76.dist-info}/RECORD +312 -175
  302. {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.76.dist-info}/WHEEL +1 -1
  303. {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.76.dist-info}/entry_points.txt +2 -0
  304. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.DWzvg0-y.css +0 -1
  305. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.ThTw9_ym.css +0 -1
  306. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/4TdZjIqw.js +0 -1
  307. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/5shd3_w0.js +0 -24
  308. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BKjSRqUr.js +0 -1
  309. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Da0KfYnO.js +0 -1
  310. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Dfy6j1xT.js +0 -323
  311. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.NWzMBYRp.js +0 -1
  312. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.C0GcWctS.js +0 -1
  313. claude_mpm-5.4.85.dist-info/METADATA +0 -1023
  314. /claude_mpm/skills/bundled/pm/{pm-bug-reporting/pm-bug-reporting.md → mpm-bug-reporting/SKILL.md} +0 -0
  315. /claude_mpm/skills/bundled/pm/{pm-delegation-patterns → mpm-delegation-patterns}/SKILL.md +0 -0
  316. /claude_mpm/skills/bundled/pm/{pm-git-file-tracking → mpm-git-file-tracking}/SKILL.md +0 -0
  317. /claude_mpm/skills/bundled/pm/{pm-pr-workflow → mpm-pr-workflow}/SKILL.md +0 -0
  318. /claude_mpm/skills/bundled/pm/{pm-ticketing-integration → mpm-ticketing-integration}/SKILL.md +0 -0
  319. /claude_mpm/skills/bundled/pm/{pm-verification-protocols → mpm-verification-protocols}/SKILL.md +0 -0
  320. {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.76.dist-info}/licenses/LICENSE +0 -0
  321. {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.76.dist-info}/licenses/LICENSE-FAQ.md +0 -0
  322. {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.76.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,270 @@
1
+ """Parse tool output and detect events."""
2
+
3
+ import logging
4
+ import re
5
+ from dataclasses import dataclass
6
+ from typing import Any, Dict, List, Optional
7
+
8
+ from ..events.manager import EventManager
9
+ from ..models.events import EventType
10
+ from .extractor import (
11
+ extract_action_details,
12
+ extract_error_context,
13
+ extract_options,
14
+ strip_code_blocks,
15
+ )
16
+ from .patterns import ALL_PATTERNS
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+ # ANSI escape code pattern
21
+ ANSI_ESCAPE = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])")
22
+
23
+
24
+ @dataclass
25
+ class ParseResult:
26
+ """Result of parsing output."""
27
+
28
+ event_type: EventType
29
+ title: str
30
+ content: str
31
+ options: Optional[List[str]] = None
32
+ context: Optional[Dict[str, Any]] = None
33
+ match_start: int = 0
34
+ match_end: int = 0
35
+
36
+
37
+ class OutputParser:
38
+ """Parses tool output and detects events.
39
+
40
+ Detects various event types including:
41
+ - Decisions needed (with option extraction)
42
+ - Approvals required (with action details)
43
+ - Errors (with context)
44
+ - Task completions
45
+ - Clarifications needed
46
+
47
+ Features:
48
+ - ANSI escape code stripping
49
+ - Code block exclusion (avoid false positives)
50
+ - Overlap deduplication
51
+ - Option extraction from various formats
52
+ - Integration with EventManager
53
+ """
54
+
55
+ def __init__(self, event_manager: Optional[EventManager] = None):
56
+ """Initialize parser.
57
+
58
+ Args:
59
+ event_manager: Optional EventManager for automatic event creation
60
+ """
61
+ self.event_manager = event_manager
62
+ self._patterns = ALL_PATTERNS
63
+
64
+ def strip_ansi(self, text: str) -> str:
65
+ """Remove ANSI escape codes.
66
+
67
+ Args:
68
+ text: Text potentially containing ANSI codes
69
+
70
+ Returns:
71
+ Text with ANSI codes removed
72
+ """
73
+ return ANSI_ESCAPE.sub("", text)
74
+
75
+ def parse(
76
+ self,
77
+ content: str,
78
+ project_id: str,
79
+ session_id: Optional[str] = None,
80
+ create_events: bool = True,
81
+ ) -> List[ParseResult]:
82
+ """Parse output and detect all events.
83
+
84
+ Args:
85
+ content: Output content to parse
86
+ project_id: Project identifier for event creation
87
+ session_id: Optional session identifier
88
+ create_events: Whether to create events via EventManager
89
+
90
+ Returns:
91
+ List of ParseResult objects for all detected events
92
+ """
93
+ results: List[ParseResult] = []
94
+
95
+ # Clean content
96
+ clean_content = self.strip_ansi(content)
97
+
98
+ # Strip code blocks for pattern matching
99
+ matchable_content = strip_code_blocks(clean_content)
100
+
101
+ # Check each pattern category
102
+ for category, patterns in self._patterns.items():
103
+ for pattern, event_type in patterns:
104
+ for match in pattern.finditer(matchable_content):
105
+ result = self._create_result(
106
+ event_type=event_type,
107
+ match=match,
108
+ original_content=clean_content,
109
+ matchable_content=matchable_content,
110
+ )
111
+ if result:
112
+ results.append(result)
113
+
114
+ # Deduplicate overlapping results
115
+ results = self._deduplicate(results)
116
+
117
+ # Create events if manager provided and flag set
118
+ if self.event_manager and create_events:
119
+ for result in results:
120
+ self.event_manager.create(
121
+ project_id=project_id,
122
+ session_id=session_id,
123
+ event_type=result.event_type,
124
+ title=result.title,
125
+ content=result.content,
126
+ options=result.options,
127
+ context=result.context,
128
+ )
129
+
130
+ logger.debug("Parsed %d events from output", len(results))
131
+ return results
132
+
133
+ def _create_result(
134
+ self,
135
+ event_type: EventType,
136
+ match: re.Match,
137
+ original_content: str,
138
+ matchable_content: str,
139
+ ) -> Optional[ParseResult]:
140
+ """Create a ParseResult from a pattern match.
141
+
142
+ Args:
143
+ event_type: Type of event detected
144
+ match: Regex match object
145
+ original_content: Original content (with code blocks)
146
+ matchable_content: Content with code blocks stripped
147
+
148
+ Returns:
149
+ ParseResult if valid, None otherwise
150
+ """
151
+ matched_text = match.group(0)
152
+ options = None
153
+ context = {}
154
+
155
+ # Generate title and extract context based on event type
156
+ if event_type == EventType.ERROR:
157
+ title = self._extract_error_title(matched_text)
158
+ context = extract_error_context(
159
+ original_content, match.start(), match.end()
160
+ )
161
+ elif event_type == EventType.DECISION_NEEDED:
162
+ title = "Decision needed"
163
+ # Look ahead for options (up to 500 chars)
164
+ options = extract_options(
165
+ original_content[match.start() : match.start() + 500]
166
+ )
167
+ context = {"options_detected": len(options) if options else 0}
168
+ elif event_type == EventType.APPROVAL:
169
+ title = "Approval required"
170
+ context = extract_action_details(original_content, match)
171
+ options = ["Yes", "No"]
172
+ elif event_type == EventType.TASK_COMPLETE:
173
+ title = "Task completed"
174
+ elif event_type == EventType.CLARIFICATION:
175
+ title = "Clarification needed"
176
+ else:
177
+ title = f"{event_type.value} detected"
178
+
179
+ return ParseResult(
180
+ event_type=event_type,
181
+ title=title,
182
+ content=matched_text[:500], # Truncate long matches
183
+ options=options
184
+ if event_type in (EventType.DECISION_NEEDED, EventType.APPROVAL)
185
+ else None,
186
+ context=context,
187
+ match_start=match.start(),
188
+ match_end=match.end(),
189
+ )
190
+
191
+ def _extract_error_title(self, matched_text: str) -> str:
192
+ """Extract a concise error title.
193
+
194
+ Args:
195
+ matched_text: Text that matched the error pattern
196
+
197
+ Returns:
198
+ Concise error title (max 80 chars)
199
+ """
200
+ # Try to get the error type and message
201
+ error_match = re.search(
202
+ r"(\w+(?:Error|Exception)): (.+?)(?:\n|$)", matched_text
203
+ )
204
+ if error_match:
205
+ error_type = error_match.group(1)
206
+ error_msg = error_match.group(2)[:50]
207
+ return f"{error_type}: {error_msg}"
208
+
209
+ # Fallback to first line
210
+ first_line = matched_text.split("\n")[0].strip()
211
+ return first_line[:80] if first_line else "Error detected"
212
+
213
+ def _deduplicate(self, results: List[ParseResult]) -> List[ParseResult]:
214
+ """Remove duplicate or overlapping results.
215
+
216
+ When results overlap, keep the one with higher priority.
217
+ Priority order: ERROR > APPROVAL > DECISION > CLARIFICATION > COMPLETION
218
+
219
+ Args:
220
+ results: List of ParseResult objects to deduplicate
221
+
222
+ Returns:
223
+ Deduplicated list of ParseResult objects
224
+ """
225
+ if not results:
226
+ return results
227
+
228
+ # Sort by position
229
+ sorted_results = sorted(results, key=lambda r: r.match_start)
230
+
231
+ # Priority order (lower index = higher priority)
232
+ priority_order = [
233
+ EventType.ERROR,
234
+ EventType.APPROVAL,
235
+ EventType.DECISION_NEEDED,
236
+ EventType.CLARIFICATION,
237
+ EventType.TASK_COMPLETE,
238
+ ]
239
+
240
+ deduped: List[ParseResult] = []
241
+ for result in sorted_results:
242
+ # Check if overlaps with any existing result
243
+ overlaps = False
244
+ for existing in deduped:
245
+ if (
246
+ result.match_start < existing.match_end
247
+ and result.match_end > existing.match_start
248
+ ):
249
+ # Overlap detected - keep higher priority
250
+ result_priority = (
251
+ priority_order.index(result.event_type)
252
+ if result.event_type in priority_order
253
+ else 99
254
+ )
255
+ existing_priority = (
256
+ priority_order.index(existing.event_type)
257
+ if existing.event_type in priority_order
258
+ else 99
259
+ )
260
+
261
+ if result_priority < existing_priority:
262
+ deduped.remove(existing)
263
+ else:
264
+ overlaps = True
265
+ break
266
+
267
+ if not overlaps:
268
+ deduped.append(result)
269
+
270
+ return deduped
@@ -0,0 +1,100 @@
1
+ """Detection patterns for various event types in tool output."""
2
+
3
+ import re
4
+ from typing import List, Tuple
5
+
6
+ from ..models.events import EventType
7
+
8
+ # Pattern format: (compiled_regex, event_type)
9
+
10
+ DECISION_PATTERNS: List[Tuple[re.Pattern, EventType]] = [
11
+ (
12
+ re.compile(
13
+ r"Which (?:option|approach|method) (?:would you|do you) prefer\?", re.I
14
+ ),
15
+ EventType.DECISION_NEEDED,
16
+ ),
17
+ (
18
+ re.compile(
19
+ r"Should I (?:proceed|continue|use|implement) (?:with )?(.+)\?", re.I
20
+ ),
21
+ EventType.DECISION_NEEDED,
22
+ ),
23
+ (re.compile(r"Do you want me to (.+)\?", re.I), EventType.DECISION_NEEDED),
24
+ (
25
+ re.compile(r"Please choose:?\s*\n(?:\s*\d+[\.\)]\s*.+\n?)+", re.I | re.M),
26
+ EventType.DECISION_NEEDED,
27
+ ),
28
+ (
29
+ re.compile(r"Options:?\s*\n(?:\s*[-•]\s*.+\n?)+", re.I | re.M),
30
+ EventType.DECISION_NEEDED,
31
+ ),
32
+ (re.compile(r"\(y/n\)\??", re.I), EventType.DECISION_NEEDED),
33
+ (re.compile(r"\[Y/n\]", re.I), EventType.DECISION_NEEDED),
34
+ (re.compile(r"\[yes/no\]", re.I), EventType.DECISION_NEEDED),
35
+ (re.compile(r"Select an option:", re.I), EventType.DECISION_NEEDED),
36
+ ]
37
+
38
+ APPROVAL_PATTERNS: List[Tuple[re.Pattern, EventType]] = [
39
+ (
40
+ re.compile(r"This will (?:delete|remove|overwrite|modify) (.+)", re.I),
41
+ EventType.APPROVAL,
42
+ ),
43
+ (re.compile(r"Are you sure you want to (.+)\?", re.I), EventType.APPROVAL),
44
+ (re.compile(r"This action cannot be undone", re.I), EventType.APPROVAL),
45
+ (re.compile(r"Warning: This will (.+)", re.I), EventType.APPROVAL),
46
+ (re.compile(r"Do you want to allow (.+)\?", re.I), EventType.APPROVAL),
47
+ (re.compile(r"Permanently delete", re.I), EventType.APPROVAL),
48
+ ]
49
+
50
+ ERROR_PATTERNS: List[Tuple[re.Pattern, EventType]] = [
51
+ (re.compile(r"Traceback \(most recent call last\):", re.I), EventType.ERROR),
52
+ (re.compile(r"(\w+Error): (.+)", re.I), EventType.ERROR),
53
+ (re.compile(r"(\w+Exception): (.+)", re.I), EventType.ERROR),
54
+ (re.compile(r"^Error: (.+)", re.I | re.M), EventType.ERROR),
55
+ (re.compile(r"^Failed: (.+)", re.I | re.M), EventType.ERROR),
56
+ (re.compile(r"^FATAL: (.+)", re.I | re.M), EventType.ERROR),
57
+ (re.compile(r"Permission denied", re.I), EventType.ERROR),
58
+ (re.compile(r"Access denied", re.I), EventType.ERROR),
59
+ (re.compile(r"(?:File|Directory) not found", re.I), EventType.ERROR),
60
+ (re.compile(r"Connection refused", re.I), EventType.ERROR),
61
+ (re.compile(r"Timeout(?:Error)?", re.I), EventType.ERROR),
62
+ (re.compile(r"✗", re.I), EventType.ERROR), # Claude Code error indicator
63
+ ]
64
+
65
+ COMPLETION_PATTERNS: List[Tuple[re.Pattern, EventType]] = [
66
+ (
67
+ re.compile(r"(?:Done|Complete|Finished|Success)[\.\!]?\s*$", re.I | re.M),
68
+ EventType.TASK_COMPLETE,
69
+ ),
70
+ (re.compile(r"Successfully (.+)", re.I), EventType.TASK_COMPLETE),
71
+ (
72
+ re.compile(r"I(?:'ve| have) (?:completed|finished|done) (.+)", re.I),
73
+ EventType.TASK_COMPLETE,
74
+ ),
75
+ (re.compile(r"Task (?:complete|finished)", re.I), EventType.TASK_COMPLETE),
76
+ ]
77
+
78
+ CLARIFICATION_PATTERNS: List[Tuple[re.Pattern, EventType]] = [
79
+ (
80
+ re.compile(
81
+ r"Could you (?:please )?(?:clarify|explain|provide more details)", re.I
82
+ ),
83
+ EventType.CLARIFICATION,
84
+ ),
85
+ (re.compile(r"I need more information about", re.I), EventType.CLARIFICATION),
86
+ (re.compile(r"What do you mean by", re.I), EventType.CLARIFICATION),
87
+ (re.compile(r"Can you be more specific", re.I), EventType.CLARIFICATION),
88
+ ]
89
+
90
+ # Patterns to ignore (inside code blocks, etc.)
91
+ CODE_BLOCK_PATTERN = re.compile(r"```[\s\S]*?```", re.M)
92
+ INLINE_CODE_PATTERN = re.compile(r"`[^`]+`")
93
+
94
+ ALL_PATTERNS = {
95
+ "decision": DECISION_PATTERNS,
96
+ "approval": APPROVAL_PATTERNS,
97
+ "error": ERROR_PATTERNS,
98
+ "completion": COMPLETION_PATTERNS,
99
+ "clarification": CLARIFICATION_PATTERNS,
100
+ }
@@ -0,0 +1,11 @@
1
+ """Persistence layer for MPM Commander.
2
+
3
+ This module provides state persistence and recovery capabilities for
4
+ the Commander daemon, including atomic writes and graceful recovery.
5
+ """
6
+
7
+ from .event_store import EventStore
8
+ from .state_store import StateStore
9
+ from .work_store import WorkStore
10
+
11
+ __all__ = ["EventStore", "StateStore", "WorkStore"]
@@ -0,0 +1,274 @@
1
+ """Event persistence for MPM Commander.
2
+
3
+ This module handles persistence and recovery of the event queue/inbox,
4
+ including append-only event logging and efficient event removal.
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
14
+
15
+ from ..models.events import Event, EventPriority, EventStatus, EventType
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ class EventStore:
21
+ """Persists and recovers events.
22
+
23
+ Provides efficient event persistence with:
24
+ - Batch save of all events
25
+ - Append-only logging for real-time persistence
26
+ - Safe event removal
27
+ - Atomic writes to prevent corruption
28
+
29
+ Attributes:
30
+ state_dir: Directory for state files
31
+ events_path: Path to events.json
32
+
33
+ Example:
34
+ >>> store = EventStore(Path("~/.claude-mpm/commander"))
35
+ >>> await store.save_events(inbox)
36
+ >>> events = await store.load_events()
37
+ """
38
+
39
+ VERSION = "1.0"
40
+
41
+ def __init__(self, state_dir: Path):
42
+ """Initialize event 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.events_path = self.state_dir / "events.json"
51
+
52
+ logger.info(f"Initialized EventStore at {self.state_dir}")
53
+
54
+ async def save_events(self, inbox: "Inbox") -> None: # noqa: F821
55
+ """Save pending events to disk.
56
+
57
+ Args:
58
+ inbox: Inbox containing events to persist
59
+
60
+ Raises:
61
+ IOError: If write fails
62
+ """
63
+ # Get all pending events from event manager
64
+ events = inbox.events.get_pending()
65
+
66
+ data = {
67
+ "version": self.VERSION,
68
+ "saved_at": datetime.now(timezone.utc).isoformat(),
69
+ "events": [self._serialize_event(e) for e in events],
70
+ }
71
+
72
+ # Run sync I/O in executor
73
+ await asyncio.get_event_loop().run_in_executor(
74
+ None, self._atomic_write, self.events_path, data
75
+ )
76
+
77
+ logger.info(f"Saved {len(events)} events to {self.events_path}")
78
+
79
+ async def load_events(self) -> List[Event]:
80
+ """Load events from disk.
81
+
82
+ Returns:
83
+ List of Event instances (empty if file missing or corrupt)
84
+ """
85
+ if not self.events_path.exists():
86
+ logger.info("No events file found, returning empty list")
87
+ return []
88
+
89
+ try:
90
+ # Run sync I/O in executor
91
+ data = await asyncio.get_event_loop().run_in_executor(
92
+ None, self._read_json, self.events_path
93
+ )
94
+
95
+ if data.get("version") != self.VERSION:
96
+ logger.warning(
97
+ f"Version mismatch: expected {self.VERSION}, "
98
+ f"got {data.get('version')}"
99
+ )
100
+
101
+ events = [self._deserialize_event(e) for e in data.get("events", [])]
102
+
103
+ logger.info(f"Loaded {len(events)} events from {self.events_path}")
104
+ return events
105
+
106
+ except Exception as e:
107
+ logger.error(f"Failed to load events: {e}", exc_info=True)
108
+ return []
109
+
110
+ async def append_event(self, event: Event) -> None:
111
+ """Append single event (for real-time persistence).
112
+
113
+ Loads existing events, adds new event, and saves atomically.
114
+ For high-frequency updates, consider batching with save_events().
115
+
116
+ Args:
117
+ event: Event to append
118
+
119
+ Raises:
120
+ IOError: If write fails
121
+ """
122
+ # Load existing events
123
+ events = await self.load_events()
124
+
125
+ # Add new event
126
+ events.append(event)
127
+
128
+ # Save back
129
+ data = {
130
+ "version": self.VERSION,
131
+ "saved_at": datetime.now(timezone.utc).isoformat(),
132
+ "events": [self._serialize_event(e) for e in events],
133
+ }
134
+
135
+ await asyncio.get_event_loop().run_in_executor(
136
+ None, self._atomic_write, self.events_path, data
137
+ )
138
+
139
+ logger.debug(f"Appended event {event.id} to {self.events_path}")
140
+
141
+ async def remove_event(self, event_id: str) -> None:
142
+ """Remove resolved event from store.
143
+
144
+ Args:
145
+ event_id: ID of event to remove
146
+
147
+ Raises:
148
+ IOError: If write fails
149
+ """
150
+ # Load existing events
151
+ events = await self.load_events()
152
+
153
+ # Filter out resolved event
154
+ filtered = [e for e in events if e.id != event_id]
155
+
156
+ if len(filtered) == len(events):
157
+ logger.warning(f"Event {event_id} not found in store")
158
+ return
159
+
160
+ # Save back
161
+ data = {
162
+ "version": self.VERSION,
163
+ "saved_at": datetime.now(timezone.utc).isoformat(),
164
+ "events": [self._serialize_event(e) for e in filtered],
165
+ }
166
+
167
+ await asyncio.get_event_loop().run_in_executor(
168
+ None, self._atomic_write, self.events_path, data
169
+ )
170
+
171
+ logger.debug(f"Removed event {event_id} from {self.events_path}")
172
+
173
+ def _atomic_write(self, path: Path, data: Dict) -> None:
174
+ """Write atomically (write to temp, then rename).
175
+
176
+ Args:
177
+ path: Target file path
178
+ data: Data to serialize as JSON
179
+
180
+ Raises:
181
+ IOError: If write fails
182
+ """
183
+ # Write to temporary file in same directory
184
+ fd, tmp_path = tempfile.mkstemp(
185
+ dir=path.parent, prefix=f".{path.name}.", suffix=".tmp"
186
+ )
187
+
188
+ try:
189
+ with open(fd, "w") as f:
190
+ json.dump(data, f, indent=2)
191
+
192
+ # Atomic rename
193
+ Path(tmp_path).rename(path)
194
+
195
+ logger.debug(f"Atomically wrote to {path}")
196
+
197
+ except Exception as e:
198
+ # Clean up temp file on error
199
+ try:
200
+ Path(tmp_path).unlink()
201
+ except Exception: # nosec B110
202
+ pass # Ignore errors during cleanup
203
+ raise OSError(f"Failed to write {path}: {e}") from e
204
+
205
+ def _read_json(self, path: Path) -> Dict:
206
+ """Read JSON file.
207
+
208
+ Args:
209
+ path: File to read
210
+
211
+ Returns:
212
+ Parsed JSON data
213
+
214
+ Raises:
215
+ IOError: If read fails
216
+ """
217
+ with open(path) as f:
218
+ return json.load(f)
219
+
220
+ def _serialize_event(self, event: Event) -> Dict[str, Any]:
221
+ """Serialize Event to JSON-compatible dict.
222
+
223
+ Args:
224
+ event: Event instance
225
+
226
+ Returns:
227
+ JSON-serializable dict
228
+ """
229
+ return {
230
+ "id": event.id,
231
+ "project_id": event.project_id,
232
+ "type": event.type.value,
233
+ "priority": event.priority.value,
234
+ "title": event.title,
235
+ "session_id": event.session_id,
236
+ "status": event.status.value,
237
+ "content": event.content,
238
+ "context": event.context,
239
+ "options": event.options,
240
+ "response": event.response,
241
+ "responded_at": (
242
+ event.responded_at.isoformat() if event.responded_at else None
243
+ ),
244
+ "created_at": event.created_at.isoformat(),
245
+ }
246
+
247
+ def _deserialize_event(self, data: Dict[str, Any]) -> Event:
248
+ """Deserialize Event from JSON dict.
249
+
250
+ Args:
251
+ data: Serialized event data
252
+
253
+ Returns:
254
+ Event instance
255
+ """
256
+ return Event(
257
+ id=data["id"],
258
+ project_id=data["project_id"],
259
+ type=EventType(data["type"]),
260
+ priority=EventPriority(data["priority"]),
261
+ title=data["title"],
262
+ session_id=data.get("session_id"),
263
+ status=EventStatus(data["status"]),
264
+ content=data.get("content", ""),
265
+ context=data.get("context", {}),
266
+ options=data.get("options"),
267
+ response=data.get("response"),
268
+ responded_at=(
269
+ datetime.fromisoformat(data["responded_at"])
270
+ if data.get("responded_at")
271
+ else None
272
+ ),
273
+ created_at=datetime.fromisoformat(data["created_at"]),
274
+ )