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,392 @@
1
+ """EventManager for MPM Commander inbox system.
2
+
3
+ Manages event lifecycle, inbox queries, and project event tracking.
4
+ """
5
+
6
+ import asyncio
7
+ import logging
8
+ import threading
9
+ import uuid
10
+ from asyncio import Queue
11
+ from datetime import datetime, timezone
12
+ from typing import Any, Callable, Dict, List, Optional
13
+
14
+ from ..models.events import (
15
+ DEFAULT_PRIORITIES,
16
+ Event,
17
+ EventPriority,
18
+ EventStatus,
19
+ EventType,
20
+ )
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+
25
+ class EventManager:
26
+ """Manages event lifecycle and inbox queries.
27
+
28
+ Thread-safe event storage with support for:
29
+ - Creating events with automatic priority assignment
30
+ - Querying pending events by project or globally
31
+ - Inbox sorting by priority then timestamp
32
+ - Responding to, acknowledging, or dismissing events
33
+ - Detecting blocking events
34
+ - Clearing project events
35
+
36
+ Example:
37
+ manager = EventManager()
38
+ event = manager.create(
39
+ project_id="proj_123",
40
+ event_type=EventType.DECISION_NEEDED,
41
+ title="Choose deployment target",
42
+ options=["staging", "production"]
43
+ )
44
+ inbox = manager.get_inbox()
45
+ manager.respond(event.id, "staging")
46
+ """
47
+
48
+ def __init__(self) -> None:
49
+ """Initialize empty event storage."""
50
+ self._events: Dict[str, Event] = {}
51
+ self._project_index: Dict[str, List[str]] = {} # project_id -> event_ids
52
+ self._lock = threading.RLock()
53
+ self._subscribers: Dict[EventType, List[Callable]] = {}
54
+ self._event_queue: Queue = Queue()
55
+
56
+ def create(
57
+ self,
58
+ project_id: str,
59
+ event_type: EventType,
60
+ title: str,
61
+ content: str = "",
62
+ session_id: Optional[str] = None,
63
+ priority: Optional[EventPriority] = None,
64
+ options: Optional[List[str]] = None,
65
+ context: Optional[Dict[str, Any]] = None,
66
+ ) -> Event:
67
+ """Create and queue a new event.
68
+
69
+ Args:
70
+ project_id: ID of project raising this event
71
+ event_type: Type of event
72
+ title: Short summary
73
+ content: Detailed description
74
+ session_id: Optional session ID
75
+ priority: Optional priority (uses default if not specified)
76
+ options: For DECISION_NEEDED, list of choices
77
+ context: Additional structured data
78
+
79
+ Returns:
80
+ The created Event
81
+
82
+ Example:
83
+ event = manager.create(
84
+ project_id="proj_123",
85
+ event_type=EventType.ERROR,
86
+ title="Database connection failed",
87
+ content="Could not connect to postgres://..."
88
+ )
89
+ """
90
+ with self._lock:
91
+ event_id = f"evt_{uuid.uuid4().hex[:12]}"
92
+
93
+ # Use default priority if not specified
94
+ if priority is None:
95
+ priority = DEFAULT_PRIORITIES.get(event_type, EventPriority.NORMAL)
96
+
97
+ event = Event(
98
+ id=event_id,
99
+ project_id=project_id,
100
+ session_id=session_id,
101
+ type=event_type,
102
+ priority=priority,
103
+ title=title,
104
+ content=content,
105
+ context=context or {},
106
+ options=options,
107
+ )
108
+
109
+ self._events[event_id] = event
110
+
111
+ # Index by project
112
+ if project_id not in self._project_index:
113
+ self._project_index[project_id] = []
114
+ self._project_index[project_id].append(event_id)
115
+
116
+ logger.info("Created event %s: [%s] %s", event_id, event_type.value, title)
117
+ return event
118
+
119
+ def add_event(self, event: Event) -> None:
120
+ """Add existing event to manager (for loading from persistence).
121
+
122
+ Args:
123
+ event: Event instance to add
124
+
125
+ Example:
126
+ # Load events from disk and add to manager
127
+ for event in loaded_events:
128
+ manager.add_event(event)
129
+ """
130
+ with self._lock:
131
+ self._events[event.id] = event
132
+
133
+ # Index by project
134
+ if event.project_id not in self._project_index:
135
+ self._project_index[event.project_id] = []
136
+ if event.id not in self._project_index[event.project_id]:
137
+ self._project_index[event.project_id].append(event.id)
138
+
139
+ logger.debug("Added event %s to manager", event.id)
140
+
141
+ def get(self, event_id: str) -> Optional[Event]:
142
+ """Get event by ID.
143
+
144
+ Args:
145
+ event_id: Unique event identifier
146
+
147
+ Returns:
148
+ Event if found, None otherwise
149
+ """
150
+ return self._events.get(event_id)
151
+
152
+ def get_pending(self, project_id: Optional[str] = None) -> List[Event]:
153
+ """Get all pending events, optionally filtered by project.
154
+
155
+ Args:
156
+ project_id: If provided, only return events for this project
157
+
158
+ Returns:
159
+ List of pending events (unsorted)
160
+
161
+ Example:
162
+ # Get all pending events
163
+ all_pending = manager.get_pending()
164
+
165
+ # Get pending events for one project
166
+ project_pending = manager.get_pending("proj_123")
167
+ """
168
+ with self._lock:
169
+ if project_id:
170
+ event_ids = self._project_index.get(project_id, [])
171
+ events = [self._events[eid] for eid in event_ids if eid in self._events]
172
+ else:
173
+ events = list(self._events.values())
174
+
175
+ return [e for e in events if e.status == EventStatus.PENDING]
176
+
177
+ def get_inbox(self, limit: int = 50) -> List[Event]:
178
+ """Get events for inbox, sorted by priority then time.
179
+
180
+ Sorting order:
181
+ 1. Priority: CRITICAL > HIGH > NORMAL > LOW > INFO
182
+ 2. Within same priority: oldest first (created_at ascending)
183
+
184
+ Args:
185
+ limit: Maximum number of events to return
186
+
187
+ Returns:
188
+ Sorted list of pending events, limited to `limit` items
189
+
190
+ Example:
191
+ inbox = manager.get_inbox(limit=20)
192
+ for event in inbox:
193
+ print(f"{event.priority.value}: {event.title}")
194
+ """
195
+ with self._lock:
196
+ pending = [
197
+ e for e in self._events.values() if e.status == EventStatus.PENDING
198
+ ]
199
+
200
+ # Sort by priority (CRITICAL first) then by created_at (oldest first)
201
+ priority_order = [
202
+ EventPriority.CRITICAL,
203
+ EventPriority.HIGH,
204
+ EventPriority.NORMAL,
205
+ EventPriority.LOW,
206
+ EventPriority.INFO,
207
+ ]
208
+
209
+ def sort_key(event: Event) -> tuple[int, datetime]:
210
+ pri_idx = (
211
+ priority_order.index(event.priority)
212
+ if event.priority in priority_order
213
+ else 99
214
+ )
215
+ return (pri_idx, event.created_at)
216
+
217
+ sorted_events = sorted(pending, key=sort_key)
218
+ return sorted_events[:limit]
219
+
220
+ def respond(self, event_id: str, response: str) -> Event:
221
+ """Record response to event and mark as resolved.
222
+
223
+ Args:
224
+ event_id: ID of event to respond to
225
+ response: User's response text
226
+
227
+ Returns:
228
+ The updated Event
229
+
230
+ Raises:
231
+ KeyError: If event_id not found
232
+
233
+ Example:
234
+ event = manager.respond("evt_abc123", "Deploy to staging")
235
+ """
236
+ with self._lock:
237
+ event = self._events.get(event_id)
238
+ if not event:
239
+ raise KeyError(f"Event not found: {event_id}")
240
+
241
+ event.response = response
242
+ event.responded_at = datetime.now(timezone.utc)
243
+ event.status = EventStatus.RESOLVED
244
+
245
+ logger.info("Responded to event %s: %s", event_id, response[:50])
246
+ return event
247
+
248
+ def dismiss(self, event_id: str) -> Event:
249
+ """Mark event as dismissed without providing a response.
250
+
251
+ Args:
252
+ event_id: ID of event to dismiss
253
+
254
+ Returns:
255
+ The updated Event
256
+
257
+ Raises:
258
+ KeyError: If event_id not found
259
+
260
+ Example:
261
+ manager.dismiss("evt_abc123")
262
+ """
263
+ with self._lock:
264
+ event = self._events.get(event_id)
265
+ if not event:
266
+ raise KeyError(f"Event not found: {event_id}")
267
+
268
+ event.status = EventStatus.DISMISSED
269
+
270
+ logger.info("Dismissed event %s", event_id)
271
+ return event
272
+
273
+ def acknowledge(self, event_id: str) -> Event:
274
+ """Mark event as seen but not resolved yet.
275
+
276
+ Args:
277
+ event_id: ID of event to acknowledge
278
+
279
+ Returns:
280
+ The updated Event
281
+
282
+ Raises:
283
+ KeyError: If event_id not found
284
+
285
+ Example:
286
+ manager.acknowledge("evt_abc123")
287
+ """
288
+ with self._lock:
289
+ event = self._events.get(event_id)
290
+ if not event:
291
+ raise KeyError(f"Event not found: {event_id}")
292
+
293
+ event.status = EventStatus.ACKNOWLEDGED
294
+
295
+ logger.info("Acknowledged event %s", event_id)
296
+ return event
297
+
298
+ def get_blocking_events(self, project_id: Optional[str] = None) -> List[Event]:
299
+ """Get events that are blocking progress.
300
+
301
+ Args:
302
+ project_id: If provided, include project-scoped blocking events
303
+ for this project. Always includes global blocking events.
304
+
305
+ Returns:
306
+ List of blocking events
307
+
308
+ Example:
309
+ # Get all blocking events (global scope only)
310
+ blockers = manager.get_blocking_events()
311
+
312
+ # Get blocking events for specific project (global + project scope)
313
+ blockers = manager.get_blocking_events("proj_123")
314
+ """
315
+ with self._lock:
316
+ pending = self.get_pending(project_id)
317
+ return [e for e in pending if e.is_blocking]
318
+
319
+ def clear_project_events(self, project_id: str) -> int:
320
+ """Clear all events for a project.
321
+
322
+ Args:
323
+ project_id: ID of project whose events should be cleared
324
+
325
+ Returns:
326
+ Number of events removed
327
+
328
+ Example:
329
+ removed = manager.clear_project_events("proj_123")
330
+ print(f"Cleared {removed} events")
331
+ """
332
+ with self._lock:
333
+ event_ids = self._project_index.pop(project_id, [])
334
+ for eid in event_ids:
335
+ self._events.pop(eid, None)
336
+ return len(event_ids)
337
+
338
+ def subscribe(self, event_type: EventType, callback: Callable) -> None:
339
+ """Subscribe callback to event type.
340
+
341
+ Args:
342
+ event_type: Type of event to subscribe to
343
+ callback: Function to call when event occurs (sync or async)
344
+
345
+ Example:
346
+ def on_error(event):
347
+ print(f"Error: {event.title}")
348
+
349
+ manager.subscribe(EventType.ERROR, on_error)
350
+ """
351
+ if event_type not in self._subscribers:
352
+ self._subscribers[event_type] = []
353
+ self._subscribers[event_type].append(callback)
354
+
355
+ def unsubscribe(self, event_type: EventType, callback: Callable) -> None:
356
+ """Unsubscribe callback from event type.
357
+
358
+ Args:
359
+ event_type: Type of event to unsubscribe from
360
+ callback: Function to remove from subscribers
361
+
362
+ Example:
363
+ manager.unsubscribe(EventType.ERROR, on_error)
364
+ """
365
+ if (
366
+ event_type in self._subscribers
367
+ and callback in self._subscribers[event_type]
368
+ ):
369
+ self._subscribers[event_type].remove(callback)
370
+
371
+ async def emit(self, event: Event) -> None:
372
+ """Emit event to all subscribers.
373
+
374
+ Queues the event and notifies all subscribed callbacks.
375
+ Supports both sync and async callbacks.
376
+
377
+ Args:
378
+ event: Event to emit
379
+
380
+ Example:
381
+ await manager.emit(event)
382
+ """
383
+ await self._event_queue.put(event)
384
+ if event.type in self._subscribers:
385
+ for callback in self._subscribers[event.type]:
386
+ try:
387
+ if asyncio.iscoroutinefunction(callback):
388
+ await callback(event)
389
+ else:
390
+ callback(event)
391
+ except Exception as e:
392
+ logger.error(f"Subscriber callback error: {e}")
@@ -0,0 +1,12 @@
1
+ """Framework abstractions for AI coding assistants."""
2
+
3
+ from .base import BaseFramework, InstanceInfo
4
+ from .claude_code import ClaudeCodeFramework
5
+ from .mpm import MPMFramework
6
+
7
+ __all__ = [
8
+ "BaseFramework",
9
+ "ClaudeCodeFramework",
10
+ "InstanceInfo",
11
+ "MPMFramework",
12
+ ]
@@ -0,0 +1,233 @@
1
+ """Base framework abstraction for different AI coding assistants."""
2
+
3
+ import subprocess # nosec B404 - Required for git operations
4
+ from abc import ABC, abstractmethod
5
+ from dataclasses import dataclass
6
+ from pathlib import Path
7
+ from typing import Optional
8
+
9
+
10
+ @dataclass
11
+ class InstanceInfo:
12
+ """Information about a running framework instance.
13
+
14
+ Attributes:
15
+ name: Instance name (e.g., "myapp")
16
+ project_path: Path to project directory
17
+ framework: Framework identifier (e.g., "cc", "mpm")
18
+ tmux_session: Tmux session name
19
+ pane_target: Tmux pane target (e.g., "%1")
20
+ git_branch: Current git branch if project is a git repo
21
+ git_status: Git status summary if project is a git repo
22
+ connected: Whether instance has an active adapter connection
23
+
24
+ Example:
25
+ >>> info = InstanceInfo(
26
+ ... name="myapp",
27
+ ... project_path=Path("/Users/user/myapp"),
28
+ ... framework="cc",
29
+ ... tmux_session="mpm-commander",
30
+ ... pane_target="%1",
31
+ ... git_branch="main",
32
+ ... git_status="clean",
33
+ ... connected=True
34
+ ... )
35
+ """
36
+
37
+ name: str
38
+ project_path: Path
39
+ framework: str
40
+ tmux_session: str
41
+ pane_target: str
42
+ git_branch: Optional[str] = None
43
+ git_status: Optional[str] = None
44
+ connected: bool = False
45
+ ready: bool = False
46
+
47
+
48
+ @dataclass
49
+ class RegisteredInstance:
50
+ """Persistent instance configuration (survives daemon restart).
51
+
52
+ Attributes:
53
+ name: Instance identifier
54
+ path: Original project directory path (stored as string for JSON)
55
+ framework: Framework identifier ("cc" or "mpm")
56
+ registered_at: ISO timestamp when instance was registered
57
+ worktree_path: Path to git worktree (if using worktree isolation)
58
+ worktree_branch: Branch name in the worktree
59
+ use_worktree: Whether worktree isolation is enabled
60
+
61
+ Example:
62
+ >>> instance = RegisteredInstance(
63
+ ... name="myapp",
64
+ ... path="/Users/user/myapp",
65
+ ... framework="cc",
66
+ ... registered_at="2024-01-15T10:30:00"
67
+ ... )
68
+ >>> instance.to_dict()
69
+ {'name': 'myapp', 'path': '/Users/user/myapp', 'framework': 'cc', ...}
70
+ >>> instance.working_path
71
+ '/Users/user/myapp'
72
+
73
+ >>> # With worktree enabled
74
+ >>> instance = RegisteredInstance(
75
+ ... name="myapp",
76
+ ... path="/Users/user/myapp",
77
+ ... framework="cc",
78
+ ... registered_at="2024-01-15T10:30:00",
79
+ ... worktree_path="/Users/user/.mpm/worktrees/myapp",
80
+ ... worktree_branch="feature/new-feature",
81
+ ... use_worktree=True
82
+ ... )
83
+ >>> instance.working_path
84
+ '/Users/user/.mpm/worktrees/myapp'
85
+ """
86
+
87
+ name: str
88
+ path: str # Original project path
89
+ framework: str
90
+ registered_at: str
91
+ # Worktree fields
92
+ worktree_path: Optional[str] = None # Path to worktree (if using)
93
+ worktree_branch: Optional[str] = None # Branch in worktree
94
+ use_worktree: bool = False # Whether worktree is enabled
95
+
96
+ def to_dict(self) -> dict:
97
+ """Serialize for JSON storage."""
98
+ return {
99
+ "name": self.name,
100
+ "path": self.path,
101
+ "framework": self.framework,
102
+ "registered_at": self.registered_at,
103
+ "worktree_path": self.worktree_path,
104
+ "worktree_branch": self.worktree_branch,
105
+ "use_worktree": self.use_worktree,
106
+ }
107
+
108
+ @classmethod
109
+ def from_dict(cls, data: dict) -> "RegisteredInstance":
110
+ """Deserialize from JSON."""
111
+ return cls(
112
+ name=data["name"],
113
+ path=data["path"],
114
+ framework=data["framework"],
115
+ registered_at=data.get("registered_at", ""),
116
+ worktree_path=data.get("worktree_path"),
117
+ worktree_branch=data.get("worktree_branch"),
118
+ use_worktree=data.get("use_worktree", False),
119
+ )
120
+
121
+ @property
122
+ def working_path(self) -> str:
123
+ """Get the actual working path (worktree or original).
124
+
125
+ Returns:
126
+ The worktree path if worktree is enabled and configured,
127
+ otherwise the original project path.
128
+ """
129
+ if self.use_worktree and self.worktree_path:
130
+ return self.worktree_path
131
+ return self.path
132
+
133
+
134
+ class BaseFramework(ABC):
135
+ """Base class for AI coding assistant frameworks.
136
+
137
+ A framework represents a specific AI coding tool (Claude Code, Claude MPM, etc.)
138
+ that can be launched in a project directory via tmux.
139
+
140
+ Attributes:
141
+ name: Short identifier (e.g., "cc", "mpm")
142
+ display_name: Human-readable name (e.g., "Claude Code", "Claude MPM")
143
+ command: The command to run (e.g., "claude")
144
+
145
+ Example:
146
+ >>> class MyFramework(BaseFramework):
147
+ ... name = "my"
148
+ ... display_name = "My Framework"
149
+ ... command = "my-command"
150
+ ...
151
+ ... def get_startup_command(self, project_path: Path) -> str:
152
+ ... return f"cd {project_path} && my-command"
153
+ ...
154
+ ... def is_available(self) -> bool:
155
+ ... return True
156
+ """
157
+
158
+ name: str # "cc", "mpm", etc.
159
+ display_name: str # "Claude Code", "Claude MPM", etc.
160
+ command: str # The command to run
161
+
162
+ @abstractmethod
163
+ def get_startup_command(self, project_path: Path) -> str:
164
+ """Get the command to start this framework in a project.
165
+
166
+ Args:
167
+ project_path: Path to the project directory
168
+
169
+ Returns:
170
+ Shell command string to start the framework
171
+
172
+ Example:
173
+ >>> framework = ClaudeCodeFramework()
174
+ >>> framework.get_startup_command(Path("/Users/user/myapp"))
175
+ "cd /Users/user/myapp && claude"
176
+ """
177
+
178
+ @abstractmethod
179
+ def is_available(self) -> bool:
180
+ """Check if this framework is installed/available.
181
+
182
+ Returns:
183
+ True if the framework command is available on the system
184
+
185
+ Example:
186
+ >>> framework = ClaudeCodeFramework()
187
+ >>> framework.is_available()
188
+ True
189
+ """
190
+
191
+ def get_git_info(self, project_path: Path) -> tuple[Optional[str], Optional[str]]:
192
+ """Get git branch and status for project.
193
+
194
+ Args:
195
+ project_path: Path to the project directory
196
+
197
+ Returns:
198
+ Tuple of (branch, status) where:
199
+ - branch: Current git branch name or None if not a git repo
200
+ - status: "clean" if no changes, "dirty" if changes, or None if not a git repo
201
+
202
+ Example:
203
+ >>> framework = ClaudeCodeFramework()
204
+ >>> branch, status = framework.get_git_info(Path("/Users/user/myapp"))
205
+ >>> print(branch, status)
206
+ main clean
207
+ """
208
+ if not (project_path / ".git").exists():
209
+ return None, None
210
+
211
+ try:
212
+ # Get current branch
213
+ result = subprocess.run( # nosec B603, B607 - Controlled git command
214
+ ["git", "-C", str(project_path), "rev-parse", "--abbrev-ref", "HEAD"],
215
+ capture_output=True,
216
+ text=True,
217
+ check=True,
218
+ )
219
+ branch = result.stdout.strip()
220
+
221
+ # Check if working directory is clean
222
+ result = subprocess.run( # nosec B603, B607 - Controlled git command
223
+ ["git", "-C", str(project_path), "status", "--porcelain"],
224
+ capture_output=True,
225
+ text=True,
226
+ check=True,
227
+ )
228
+ status = "clean" if not result.stdout.strip() else "dirty"
229
+
230
+ return branch, status
231
+
232
+ except (subprocess.CalledProcessError, FileNotFoundError):
233
+ return None, None
@@ -0,0 +1,58 @@
1
+ """Claude Code (cc) framework implementation."""
2
+
3
+ import shlex
4
+ import shutil
5
+ from pathlib import Path
6
+
7
+ from .base import BaseFramework
8
+
9
+
10
+ class ClaudeCodeFramework(BaseFramework):
11
+ """Claude Code CLI framework.
12
+
13
+ This framework launches the standard Claude Code CLI in a project directory.
14
+ It uses the 'claude' command with appropriate flags for automated operation.
15
+
16
+ Example:
17
+ >>> framework = ClaudeCodeFramework()
18
+ >>> framework.name
19
+ 'cc'
20
+ >>> framework.is_available()
21
+ True
22
+ >>> framework.get_startup_command(Path("/Users/user/myapp"))
23
+ "cd '/Users/user/myapp' && claude --dangerously-skip-permissions"
24
+ """
25
+
26
+ name = "cc"
27
+ display_name = "Claude Code"
28
+ command = "claude"
29
+
30
+ def get_startup_command(self, project_path: Path) -> str:
31
+ """Get the command to start Claude Code in a project.
32
+
33
+ Args:
34
+ project_path: Path to the project directory
35
+
36
+ Returns:
37
+ Shell command string to start Claude Code
38
+
39
+ Example:
40
+ >>> framework = ClaudeCodeFramework()
41
+ >>> framework.get_startup_command(Path("/Users/user/myapp"))
42
+ "cd '/Users/user/myapp' && claude --dangerously-skip-permissions"
43
+ """
44
+ quoted_path = shlex.quote(str(project_path))
45
+ return f"cd {quoted_path} && claude --dangerously-skip-permissions"
46
+
47
+ def is_available(self) -> bool:
48
+ """Check if 'claude' command is available.
49
+
50
+ Returns:
51
+ True if 'claude' command exists in PATH
52
+
53
+ Example:
54
+ >>> framework = ClaudeCodeFramework()
55
+ >>> framework.is_available()
56
+ True
57
+ """
58
+ return shutil.which("claude") is not None