claude-mpm 5.4.22__py3-none-any.whl → 5.6.34__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 (487) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/BASE_AGENT.md +164 -0
  3. claude_mpm/agents/BASE_ENGINEER.md +658 -0
  4. claude_mpm/agents/CLAUDE_MPM_OUTPUT_STYLE.md +66 -241
  5. claude_mpm/agents/CLAUDE_MPM_RESEARCH_OUTPUT_STYLE.md +413 -0
  6. claude_mpm/agents/CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md +109 -1925
  7. claude_mpm/agents/MEMORY.md +1 -1
  8. claude_mpm/agents/PM_INSTRUCTIONS.md +374 -1257
  9. claude_mpm/agents/WORKFLOW.md +6 -253
  10. claude_mpm/agents/agent_loader.py +1 -1
  11. claude_mpm/agents/base_agent.json +31 -0
  12. claude_mpm/agents/frontmatter_validator.py +2 -2
  13. claude_mpm/agents/templates/circuit-breakers.md +26 -17
  14. claude_mpm/cli/__init__.py +5 -1
  15. claude_mpm/cli/commands/agent_state_manager.py +10 -10
  16. claude_mpm/cli/commands/agents.py +11 -13
  17. claude_mpm/cli/commands/agents_reconcile.py +197 -0
  18. claude_mpm/cli/commands/auto_configure.py +4 -4
  19. claude_mpm/cli/commands/autotodos.py +566 -0
  20. claude_mpm/cli/commands/commander.py +216 -0
  21. claude_mpm/cli/commands/configure.py +621 -22
  22. claude_mpm/cli/commands/configure_agent_display.py +12 -0
  23. claude_mpm/cli/commands/hook_errors.py +60 -60
  24. claude_mpm/cli/commands/monitor.py +2 -2
  25. claude_mpm/cli/commands/mpm_init/core.py +72 -0
  26. claude_mpm/cli/commands/postmortem.py +1 -1
  27. claude_mpm/cli/commands/profile.py +276 -0
  28. claude_mpm/cli/commands/run.py +35 -3
  29. claude_mpm/cli/commands/skill_source.py +51 -2
  30. claude_mpm/cli/commands/skills.py +182 -32
  31. claude_mpm/cli/executor.py +130 -16
  32. claude_mpm/cli/interactive/__init__.py +10 -0
  33. claude_mpm/cli/interactive/agent_wizard.py +32 -52
  34. claude_mpm/cli/interactive/questionary_styles.py +65 -0
  35. claude_mpm/cli/interactive/skill_selector.py +481 -0
  36. claude_mpm/cli/parsers/base_parser.py +83 -1
  37. claude_mpm/cli/parsers/commander_parser.py +116 -0
  38. claude_mpm/cli/parsers/profile_parser.py +147 -0
  39. claude_mpm/cli/parsers/run_parser.py +10 -0
  40. claude_mpm/cli/parsers/skill_source_parser.py +4 -0
  41. claude_mpm/cli/parsers/skills_parser.py +2 -3
  42. claude_mpm/cli/startup.py +690 -386
  43. claude_mpm/cli/startup_display.py +74 -6
  44. claude_mpm/cli/startup_logging.py +2 -2
  45. claude_mpm/cli/utils.py +7 -3
  46. claude_mpm/commander/__init__.py +78 -0
  47. claude_mpm/commander/adapters/__init__.py +60 -0
  48. claude_mpm/commander/adapters/auggie.py +260 -0
  49. claude_mpm/commander/adapters/base.py +288 -0
  50. claude_mpm/commander/adapters/claude_code.py +392 -0
  51. claude_mpm/commander/adapters/codex.py +237 -0
  52. claude_mpm/commander/adapters/communication.py +366 -0
  53. claude_mpm/commander/adapters/example_usage.py +310 -0
  54. claude_mpm/commander/adapters/mpm.py +389 -0
  55. claude_mpm/commander/adapters/registry.py +204 -0
  56. claude_mpm/commander/api/__init__.py +16 -0
  57. claude_mpm/commander/api/app.py +121 -0
  58. claude_mpm/commander/api/errors.py +133 -0
  59. claude_mpm/commander/api/routes/__init__.py +8 -0
  60. claude_mpm/commander/api/routes/events.py +184 -0
  61. claude_mpm/commander/api/routes/inbox.py +171 -0
  62. claude_mpm/commander/api/routes/messages.py +148 -0
  63. claude_mpm/commander/api/routes/projects.py +271 -0
  64. claude_mpm/commander/api/routes/sessions.py +226 -0
  65. claude_mpm/commander/api/routes/work.py +296 -0
  66. claude_mpm/commander/api/schemas.py +186 -0
  67. claude_mpm/commander/chat/__init__.py +7 -0
  68. claude_mpm/commander/chat/cli.py +146 -0
  69. claude_mpm/commander/chat/commands.py +96 -0
  70. claude_mpm/commander/chat/repl.py +310 -0
  71. claude_mpm/commander/config.py +51 -0
  72. claude_mpm/commander/config_loader.py +115 -0
  73. claude_mpm/commander/core/__init__.py +10 -0
  74. claude_mpm/commander/core/block_manager.py +325 -0
  75. claude_mpm/commander/core/response_manager.py +323 -0
  76. claude_mpm/commander/daemon.py +603 -0
  77. claude_mpm/commander/env_loader.py +59 -0
  78. claude_mpm/commander/events/__init__.py +26 -0
  79. claude_mpm/commander/events/manager.py +332 -0
  80. claude_mpm/commander/frameworks/__init__.py +12 -0
  81. claude_mpm/commander/frameworks/base.py +146 -0
  82. claude_mpm/commander/frameworks/claude_code.py +58 -0
  83. claude_mpm/commander/frameworks/mpm.py +62 -0
  84. claude_mpm/commander/inbox/__init__.py +16 -0
  85. claude_mpm/commander/inbox/dedup.py +128 -0
  86. claude_mpm/commander/inbox/inbox.py +224 -0
  87. claude_mpm/commander/inbox/models.py +70 -0
  88. claude_mpm/commander/instance_manager.py +450 -0
  89. claude_mpm/commander/llm/__init__.py +6 -0
  90. claude_mpm/commander/llm/openrouter_client.py +167 -0
  91. claude_mpm/commander/llm/summarizer.py +70 -0
  92. claude_mpm/commander/memory/__init__.py +45 -0
  93. claude_mpm/commander/memory/compression.py +347 -0
  94. claude_mpm/commander/memory/embeddings.py +230 -0
  95. claude_mpm/commander/memory/entities.py +310 -0
  96. claude_mpm/commander/memory/example_usage.py +290 -0
  97. claude_mpm/commander/memory/integration.py +325 -0
  98. claude_mpm/commander/memory/search.py +381 -0
  99. claude_mpm/commander/memory/store.py +657 -0
  100. claude_mpm/commander/models/__init__.py +18 -0
  101. claude_mpm/commander/models/events.py +121 -0
  102. claude_mpm/commander/models/project.py +162 -0
  103. claude_mpm/commander/models/work.py +214 -0
  104. claude_mpm/commander/parsing/__init__.py +20 -0
  105. claude_mpm/commander/parsing/extractor.py +132 -0
  106. claude_mpm/commander/parsing/output_parser.py +270 -0
  107. claude_mpm/commander/parsing/patterns.py +100 -0
  108. claude_mpm/commander/persistence/__init__.py +11 -0
  109. claude_mpm/commander/persistence/event_store.py +274 -0
  110. claude_mpm/commander/persistence/state_store.py +309 -0
  111. claude_mpm/commander/persistence/work_store.py +164 -0
  112. claude_mpm/commander/polling/__init__.py +13 -0
  113. claude_mpm/commander/polling/event_detector.py +104 -0
  114. claude_mpm/commander/polling/output_buffer.py +49 -0
  115. claude_mpm/commander/polling/output_poller.py +153 -0
  116. claude_mpm/commander/project_session.py +268 -0
  117. claude_mpm/commander/proxy/__init__.py +12 -0
  118. claude_mpm/commander/proxy/formatter.py +89 -0
  119. claude_mpm/commander/proxy/output_handler.py +191 -0
  120. claude_mpm/commander/proxy/relay.py +155 -0
  121. claude_mpm/commander/registry.py +410 -0
  122. claude_mpm/commander/runtime/__init__.py +10 -0
  123. claude_mpm/commander/runtime/executor.py +191 -0
  124. claude_mpm/commander/runtime/monitor.py +346 -0
  125. claude_mpm/commander/session/__init__.py +6 -0
  126. claude_mpm/commander/session/context.py +81 -0
  127. claude_mpm/commander/session/manager.py +59 -0
  128. claude_mpm/commander/tmux_orchestrator.py +361 -0
  129. claude_mpm/commander/web/__init__.py +1 -0
  130. claude_mpm/commander/work/__init__.py +30 -0
  131. claude_mpm/commander/work/executor.py +207 -0
  132. claude_mpm/commander/work/queue.py +405 -0
  133. claude_mpm/commander/workflow/__init__.py +27 -0
  134. claude_mpm/commander/workflow/event_handler.py +241 -0
  135. claude_mpm/commander/workflow/notifier.py +146 -0
  136. claude_mpm/commands/mpm-config.md +20 -249
  137. claude_mpm/commands/mpm-doctor.md +16 -21
  138. claude_mpm/commands/mpm-help.md +12 -205
  139. claude_mpm/commands/mpm-init.md +88 -506
  140. claude_mpm/commands/mpm-monitor.md +22 -401
  141. claude_mpm/commands/mpm-organize.md +70 -442
  142. claude_mpm/commands/mpm-postmortem.md +13 -107
  143. claude_mpm/commands/mpm-session-resume.md +20 -363
  144. claude_mpm/commands/mpm-status.md +13 -69
  145. claude_mpm/commands/mpm-ticket-view.md +60 -495
  146. claude_mpm/commands/mpm-version.md +13 -107
  147. claude_mpm/commands/mpm.md +8 -0
  148. claude_mpm/config/agent_presets.py +8 -7
  149. claude_mpm/config/skill_sources.py +16 -0
  150. claude_mpm/constants.py +1 -0
  151. claude_mpm/core/claude_runner.py +154 -2
  152. claude_mpm/core/config.py +37 -26
  153. claude_mpm/core/config_constants.py +74 -9
  154. claude_mpm/core/constants.py +56 -12
  155. claude_mpm/core/framework/loaders/agent_loader.py +1 -1
  156. claude_mpm/core/framework/loaders/instruction_loader.py +52 -11
  157. claude_mpm/core/hook_manager.py +51 -3
  158. claude_mpm/core/interactive_session.py +12 -11
  159. claude_mpm/core/logger.py +26 -9
  160. claude_mpm/core/logging_utils.py +39 -13
  161. claude_mpm/core/network_config.py +148 -0
  162. claude_mpm/core/oneshot_session.py +7 -6
  163. claude_mpm/core/optimized_startup.py +61 -0
  164. claude_mpm/core/output_style_manager.py +66 -18
  165. claude_mpm/core/shared/config_loader.py +3 -1
  166. claude_mpm/core/socketio_pool.py +47 -15
  167. claude_mpm/core/unified_agent_registry.py +1 -1
  168. claude_mpm/core/unified_config.py +54 -8
  169. claude_mpm/core/unified_paths.py +95 -90
  170. claude_mpm/dashboard/static/svelte-build/_app/env.js +1 -0
  171. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.C33zOoyM.css +1 -0
  172. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.CW1J-YuA.css +1 -0
  173. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/1WZnGYqX.js +24 -0
  174. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/67pF3qNn.js +1 -0
  175. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/6RxdMKe4.js +1 -0
  176. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/8cZrfX0h.js +60 -0
  177. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/9a6T2nm-.js +7 -0
  178. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/B443AUzu.js +1 -0
  179. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/B8AwtY2H.js +1 -0
  180. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BF15LAsF.js +1 -0
  181. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BQaXIfA_.js +331 -0
  182. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BRcwIQNr.js +4 -0
  183. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BSNlmTZj.js +1 -0
  184. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BV6nKitt.js +43 -0
  185. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BViJ8lZt.js +128 -0
  186. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BcQ-Q0FE.js +1 -0
  187. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Bpyvgze_.js +30 -0
  188. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BzTRqg-z.js +1 -0
  189. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C0Fr8dve.js +1 -0
  190. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C3rbW_a-.js +1 -0
  191. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C8WYN38h.js +1 -0
  192. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C9I8FlXH.js +61 -0
  193. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CIQcWgO2.js +36 -0
  194. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CIctN7YN.js +7 -0
  195. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CKrS_JZW.js +145 -0
  196. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CR6P9C4A.js +89 -0
  197. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CRRR9MD_.js +2 -0
  198. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CRcR2DqT.js +334 -0
  199. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CSXtMOf0.js +1 -0
  200. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CT-sbxSk.js +1 -0
  201. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CWm6DJsp.js +1 -0
  202. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CmKTTxBW.js +1 -0
  203. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CpqQ1Kzn.js +1 -0
  204. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Cu_Erd72.js +261 -0
  205. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/D2nGpDRe.js +1 -0
  206. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/D9iCMida.js +267 -0
  207. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/D9ykgMoY.js +10 -0
  208. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DL2Ldur1.js +1 -0
  209. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DPfltzjH.js +165 -0
  210. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DR8nis88.js +2 -0
  211. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DUliQN2b.js +1 -0
  212. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DVp1hx9R.js +1 -0
  213. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DXlhR01x.js +122 -0
  214. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/D_lyTybS.js +1 -0
  215. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DngoTTgh.js +1 -0
  216. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DqkmHtDC.js +220 -0
  217. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DsDh8EYs.js +1 -0
  218. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DypDmXgd.js +139 -0
  219. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Gi6I4Gst.js +1 -0
  220. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/IPYC-LnN.js +162 -0
  221. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/JTLiF7dt.js +24 -0
  222. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/JpevfAFt.js +68 -0
  223. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/NqQ1dWOy.js +1 -0
  224. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/R8CEIRAd.js +2 -0
  225. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Zxy7qc-l.js +64 -0
  226. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/q9Hm6zAU.js +1 -0
  227. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/qtd3IeO4.js +15 -0
  228. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/ulBFON_C.js +65 -0
  229. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/wQVh1CoA.js +10 -0
  230. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/app.Dr7t0z2J.js +2 -0
  231. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.BGhZHUS3.js +1 -0
  232. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/0.RgBboRvH.js +1 -0
  233. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/1.DG-KkbDf.js +1 -0
  234. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.D_jnf-x6.js +1 -0
  235. claude_mpm/dashboard/static/svelte-build/_app/version.json +1 -0
  236. claude_mpm/dashboard/static/svelte-build/favicon.svg +7 -0
  237. claude_mpm/dashboard/static/svelte-build/index.html +36 -0
  238. claude_mpm/dashboard-svelte/node_modules/katex/src/fonts/generate_fonts.py +58 -0
  239. claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/extract_tfms.py +114 -0
  240. claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/extract_ttfs.py +122 -0
  241. claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/format_json.py +28 -0
  242. claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/parse_tfm.py +211 -0
  243. claude_mpm/experimental/cli_enhancements.py +2 -1
  244. claude_mpm/hooks/claude_hooks/INTEGRATION_EXAMPLE.md +243 -0
  245. claude_mpm/hooks/claude_hooks/README_AUTO_PAUSE.md +403 -0
  246. claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-311.pyc +0 -0
  247. claude_mpm/hooks/claude_hooks/__pycache__/auto_pause_handler.cpython-311.pyc +0 -0
  248. claude_mpm/hooks/claude_hooks/__pycache__/correlation_manager.cpython-311.pyc +0 -0
  249. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-311.pyc +0 -0
  250. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-311.pyc +0 -0
  251. claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-311.pyc +0 -0
  252. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-311.pyc +0 -0
  253. claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-311.pyc +0 -0
  254. claude_mpm/hooks/claude_hooks/auto_pause_handler.py +485 -0
  255. claude_mpm/hooks/claude_hooks/event_handlers.py +527 -136
  256. claude_mpm/hooks/claude_hooks/hook_handler.py +313 -99
  257. claude_mpm/hooks/claude_hooks/hook_wrapper.sh +6 -11
  258. claude_mpm/hooks/claude_hooks/installer.py +206 -36
  259. claude_mpm/hooks/claude_hooks/memory_integration.py +52 -32
  260. claude_mpm/hooks/claude_hooks/response_tracking.py +43 -60
  261. claude_mpm/hooks/claude_hooks/services/__init__.py +21 -0
  262. claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-311.pyc +0 -0
  263. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-311.pyc +0 -0
  264. claude_mpm/hooks/claude_hooks/services/__pycache__/container.cpython-311.pyc +0 -0
  265. claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-311.pyc +0 -0
  266. claude_mpm/hooks/claude_hooks/services/__pycache__/protocols.cpython-311.pyc +0 -0
  267. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-311.pyc +0 -0
  268. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-311.pyc +0 -0
  269. claude_mpm/hooks/claude_hooks/services/connection_manager.py +67 -32
  270. claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +38 -105
  271. claude_mpm/hooks/claude_hooks/services/container.py +310 -0
  272. claude_mpm/hooks/claude_hooks/services/protocols.py +328 -0
  273. claude_mpm/hooks/claude_hooks/services/state_manager.py +25 -38
  274. claude_mpm/hooks/claude_hooks/services/subagent_processor.py +75 -77
  275. claude_mpm/hooks/kuzu_memory_hook.py +5 -5
  276. claude_mpm/hooks/session_resume_hook.py +89 -1
  277. claude_mpm/hooks/templates/pre_tool_use_simple.py +6 -6
  278. claude_mpm/hooks/templates/pre_tool_use_template.py +16 -8
  279. claude_mpm/init.py +276 -0
  280. claude_mpm/models/git_repository.py +3 -3
  281. claude_mpm/scripts/claude-hook-handler.sh +46 -19
  282. claude_mpm/services/agents/agent_builder.py +3 -3
  283. claude_mpm/services/agents/agent_recommendation_service.py +8 -8
  284. claude_mpm/services/agents/agent_selection_service.py +2 -2
  285. claude_mpm/services/agents/cache_git_manager.py +7 -7
  286. claude_mpm/services/agents/deployment/agent_deployment.py +29 -7
  287. claude_mpm/services/agents/deployment/agent_discovery_service.py +4 -2
  288. claude_mpm/services/agents/deployment/agent_format_converter.py +25 -13
  289. claude_mpm/services/agents/deployment/agent_template_builder.py +39 -19
  290. claude_mpm/services/agents/deployment/agents_directory_resolver.py +2 -2
  291. claude_mpm/services/agents/deployment/async_agent_deployment.py +31 -27
  292. claude_mpm/services/agents/deployment/deployment_reconciler.py +577 -0
  293. claude_mpm/services/agents/deployment/local_template_deployment.py +3 -1
  294. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +169 -26
  295. claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +101 -75
  296. claude_mpm/services/agents/deployment/startup_reconciliation.py +138 -0
  297. claude_mpm/services/agents/git_source_manager.py +23 -4
  298. claude_mpm/services/agents/loading/framework_agent_loader.py +75 -2
  299. claude_mpm/services/agents/recommender.py +5 -3
  300. claude_mpm/services/agents/single_tier_deployment_service.py +6 -6
  301. claude_mpm/services/agents/sources/git_source_sync_service.py +121 -10
  302. claude_mpm/services/agents/startup_sync.py +27 -4
  303. claude_mpm/services/cli/__init__.py +3 -0
  304. claude_mpm/services/cli/incremental_pause_manager.py +561 -0
  305. claude_mpm/services/cli/session_resume_helper.py +10 -2
  306. claude_mpm/services/command_deployment_service.py +44 -26
  307. claude_mpm/services/delegation_detector.py +175 -0
  308. claude_mpm/services/diagnostics/checks/agent_check.py +2 -2
  309. claude_mpm/services/diagnostics/checks/agent_sources_check.py +31 -1
  310. claude_mpm/services/diagnostics/checks/configuration_check.py +24 -0
  311. claude_mpm/services/diagnostics/checks/installation_check.py +22 -0
  312. claude_mpm/services/diagnostics/checks/mcp_services_check.py +23 -0
  313. claude_mpm/services/diagnostics/doctor_reporter.py +31 -1
  314. claude_mpm/services/diagnostics/models.py +14 -1
  315. claude_mpm/services/event_log.py +325 -0
  316. claude_mpm/services/git/git_operations_service.py +8 -8
  317. claude_mpm/services/hook_installer_service.py +77 -8
  318. claude_mpm/services/infrastructure/__init__.py +4 -0
  319. claude_mpm/services/infrastructure/context_usage_tracker.py +291 -0
  320. claude_mpm/services/infrastructure/resume_log_generator.py +24 -5
  321. claude_mpm/services/monitor/daemon_manager.py +15 -4
  322. claude_mpm/services/monitor/management/lifecycle.py +15 -3
  323. claude_mpm/services/monitor/server.py +571 -11
  324. claude_mpm/services/pm_skills_deployer.py +884 -0
  325. claude_mpm/services/profile_manager.py +337 -0
  326. claude_mpm/services/skills/git_skill_source_manager.py +281 -20
  327. claude_mpm/services/skills/selective_skill_deployer.py +211 -46
  328. claude_mpm/services/skills/skill_discovery_service.py +74 -4
  329. claude_mpm/services/skills_deployer.py +192 -70
  330. claude_mpm/services/socketio/dashboard_server.py +1 -0
  331. claude_mpm/services/socketio/event_normalizer.py +37 -6
  332. claude_mpm/services/socketio/handlers/hook.py +14 -7
  333. claude_mpm/services/socketio/server/core.py +262 -123
  334. claude_mpm/services/socketio/server/main.py +12 -4
  335. claude_mpm/skills/__init__.py +2 -1
  336. claude_mpm/skills/bundled/collaboration/brainstorming/SKILL.md +79 -0
  337. claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/SKILL.md +178 -0
  338. claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/agent-prompts.md +577 -0
  339. claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/coordination-patterns.md +467 -0
  340. claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/examples.md +537 -0
  341. claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/troubleshooting.md +730 -0
  342. claude_mpm/skills/bundled/collaboration/git-worktrees.md +317 -0
  343. claude_mpm/skills/bundled/collaboration/requesting-code-review/SKILL.md +112 -0
  344. claude_mpm/skills/bundled/collaboration/requesting-code-review/references/code-reviewer-template.md +146 -0
  345. claude_mpm/skills/bundled/collaboration/requesting-code-review/references/review-examples.md +412 -0
  346. claude_mpm/skills/bundled/collaboration/stacked-prs.md +251 -0
  347. claude_mpm/skills/bundled/collaboration/writing-plans/SKILL.md +81 -0
  348. claude_mpm/skills/bundled/collaboration/writing-plans/references/best-practices.md +362 -0
  349. claude_mpm/skills/bundled/collaboration/writing-plans/references/plan-structure-templates.md +312 -0
  350. claude_mpm/skills/bundled/debugging/root-cause-tracing/SKILL.md +152 -0
  351. claude_mpm/skills/bundled/debugging/root-cause-tracing/references/advanced-techniques.md +668 -0
  352. claude_mpm/skills/bundled/debugging/root-cause-tracing/references/examples.md +587 -0
  353. claude_mpm/skills/bundled/debugging/root-cause-tracing/references/integration.md +438 -0
  354. claude_mpm/skills/bundled/debugging/root-cause-tracing/references/tracing-techniques.md +391 -0
  355. claude_mpm/skills/bundled/debugging/systematic-debugging/CREATION-LOG.md +119 -0
  356. claude_mpm/skills/bundled/debugging/systematic-debugging/SKILL.md +148 -0
  357. claude_mpm/skills/bundled/debugging/systematic-debugging/references/anti-patterns.md +483 -0
  358. claude_mpm/skills/bundled/debugging/systematic-debugging/references/examples.md +452 -0
  359. claude_mpm/skills/bundled/debugging/systematic-debugging/references/troubleshooting.md +449 -0
  360. claude_mpm/skills/bundled/debugging/systematic-debugging/references/workflow.md +411 -0
  361. claude_mpm/skills/bundled/debugging/systematic-debugging/test-academic.md +14 -0
  362. claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-1.md +58 -0
  363. claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-2.md +68 -0
  364. claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-3.md +69 -0
  365. claude_mpm/skills/bundled/debugging/verification-before-completion/SKILL.md +131 -0
  366. claude_mpm/skills/bundled/debugging/verification-before-completion/references/gate-function.md +325 -0
  367. claude_mpm/skills/bundled/debugging/verification-before-completion/references/integration-and-workflows.md +490 -0
  368. claude_mpm/skills/bundled/debugging/verification-before-completion/references/red-flags-and-failures.md +425 -0
  369. claude_mpm/skills/bundled/debugging/verification-before-completion/references/verification-patterns.md +499 -0
  370. claude_mpm/skills/bundled/infrastructure/env-manager/INTEGRATION.md +611 -0
  371. claude_mpm/skills/bundled/infrastructure/env-manager/README.md +596 -0
  372. claude_mpm/skills/bundled/infrastructure/env-manager/SKILL.md +260 -0
  373. claude_mpm/skills/bundled/infrastructure/env-manager/examples/nextjs-env-structure.md +315 -0
  374. claude_mpm/skills/bundled/infrastructure/env-manager/references/frameworks.md +436 -0
  375. claude_mpm/skills/bundled/infrastructure/env-manager/references/security.md +433 -0
  376. claude_mpm/skills/bundled/infrastructure/env-manager/references/synchronization.md +452 -0
  377. claude_mpm/skills/bundled/infrastructure/env-manager/references/troubleshooting.md +404 -0
  378. claude_mpm/skills/bundled/infrastructure/env-manager/references/validation.md +420 -0
  379. claude_mpm/skills/bundled/main/artifacts-builder/SKILL.md +86 -0
  380. claude_mpm/skills/bundled/main/internal-comms/SKILL.md +43 -0
  381. claude_mpm/skills/bundled/main/internal-comms/examples/3p-updates.md +47 -0
  382. claude_mpm/skills/bundled/main/internal-comms/examples/company-newsletter.md +65 -0
  383. claude_mpm/skills/bundled/main/internal-comms/examples/faq-answers.md +30 -0
  384. claude_mpm/skills/bundled/main/internal-comms/examples/general-comms.md +16 -0
  385. claude_mpm/skills/bundled/main/mcp-builder/SKILL.md +160 -0
  386. claude_mpm/skills/bundled/main/mcp-builder/reference/design_principles.md +412 -0
  387. claude_mpm/skills/bundled/main/mcp-builder/reference/evaluation.md +602 -0
  388. claude_mpm/skills/bundled/main/mcp-builder/reference/mcp_best_practices.md +915 -0
  389. claude_mpm/skills/bundled/main/mcp-builder/reference/node_mcp_server.md +916 -0
  390. claude_mpm/skills/bundled/main/mcp-builder/reference/python_mcp_server.md +752 -0
  391. claude_mpm/skills/bundled/main/mcp-builder/reference/workflow.md +1237 -0
  392. claude_mpm/skills/bundled/main/skill-creator/SKILL.md +189 -0
  393. claude_mpm/skills/bundled/main/skill-creator/references/best-practices.md +500 -0
  394. claude_mpm/skills/bundled/main/skill-creator/references/creation-workflow.md +464 -0
  395. claude_mpm/skills/bundled/main/skill-creator/references/examples.md +619 -0
  396. claude_mpm/skills/bundled/main/skill-creator/references/progressive-disclosure.md +437 -0
  397. claude_mpm/skills/bundled/main/skill-creator/references/skill-structure.md +231 -0
  398. claude_mpm/skills/bundled/php/espocrm-development/SKILL.md +170 -0
  399. claude_mpm/skills/bundled/php/espocrm-development/references/architecture.md +602 -0
  400. claude_mpm/skills/bundled/php/espocrm-development/references/common-tasks.md +821 -0
  401. claude_mpm/skills/bundled/php/espocrm-development/references/development-workflow.md +742 -0
  402. claude_mpm/skills/bundled/php/espocrm-development/references/frontend-customization.md +726 -0
  403. claude_mpm/skills/bundled/php/espocrm-development/references/hooks-and-services.md +764 -0
  404. claude_mpm/skills/bundled/php/espocrm-development/references/testing-debugging.md +831 -0
  405. claude_mpm/skills/bundled/pm/mpm/SKILL.md +38 -0
  406. claude_mpm/skills/bundled/pm/mpm-agent-update-workflow/SKILL.md +75 -0
  407. claude_mpm/skills/bundled/pm/mpm-bug-reporting/SKILL.md +248 -0
  408. claude_mpm/skills/bundled/pm/mpm-circuit-breaker-enforcement/SKILL.md +476 -0
  409. claude_mpm/skills/bundled/pm/mpm-config/SKILL.md +29 -0
  410. claude_mpm/skills/bundled/pm/mpm-delegation-patterns/SKILL.md +167 -0
  411. claude_mpm/skills/bundled/pm/mpm-doctor/SKILL.md +53 -0
  412. claude_mpm/skills/bundled/pm/mpm-git-file-tracking/SKILL.md +113 -0
  413. claude_mpm/skills/bundled/pm/mpm-help/SKILL.md +35 -0
  414. claude_mpm/skills/bundled/pm/mpm-init/SKILL.md +125 -0
  415. claude_mpm/skills/bundled/pm/mpm-monitor/SKILL.md +32 -0
  416. claude_mpm/skills/bundled/pm/mpm-organize/SKILL.md +121 -0
  417. claude_mpm/skills/bundled/pm/mpm-postmortem/SKILL.md +22 -0
  418. claude_mpm/skills/bundled/pm/mpm-pr-workflow/SKILL.md +124 -0
  419. claude_mpm/skills/bundled/pm/mpm-session-management/SKILL.md +312 -0
  420. claude_mpm/skills/bundled/pm/mpm-session-pause/SKILL.md +170 -0
  421. claude_mpm/skills/bundled/pm/mpm-session-resume/SKILL.md +31 -0
  422. claude_mpm/skills/bundled/pm/mpm-status/SKILL.md +37 -0
  423. claude_mpm/skills/bundled/pm/mpm-teaching-mode/SKILL.md +657 -0
  424. claude_mpm/skills/bundled/pm/mpm-ticket-view/SKILL.md +110 -0
  425. claude_mpm/skills/bundled/pm/mpm-ticketing-integration/SKILL.md +154 -0
  426. claude_mpm/skills/bundled/pm/mpm-tool-usage-guide/SKILL.md +386 -0
  427. claude_mpm/skills/bundled/pm/mpm-verification-protocols/SKILL.md +198 -0
  428. claude_mpm/skills/bundled/pm/mpm-version/SKILL.md +21 -0
  429. claude_mpm/skills/bundled/react/flexlayout-react.md +742 -0
  430. claude_mpm/skills/bundled/rust/desktop-applications/SKILL.md +226 -0
  431. claude_mpm/skills/bundled/rust/desktop-applications/references/architecture-patterns.md +901 -0
  432. claude_mpm/skills/bundled/rust/desktop-applications/references/native-gui-frameworks.md +901 -0
  433. claude_mpm/skills/bundled/rust/desktop-applications/references/platform-integration.md +775 -0
  434. claude_mpm/skills/bundled/rust/desktop-applications/references/state-management.md +937 -0
  435. claude_mpm/skills/bundled/rust/desktop-applications/references/tauri-framework.md +770 -0
  436. claude_mpm/skills/bundled/rust/desktop-applications/references/testing-deployment.md +961 -0
  437. claude_mpm/skills/bundled/security-scanning.md +112 -0
  438. claude_mpm/skills/bundled/tauri/tauri-async-patterns.md +495 -0
  439. claude_mpm/skills/bundled/tauri/tauri-build-deploy.md +599 -0
  440. claude_mpm/skills/bundled/tauri/tauri-command-patterns.md +535 -0
  441. claude_mpm/skills/bundled/tauri/tauri-error-handling.md +613 -0
  442. claude_mpm/skills/bundled/tauri/tauri-event-system.md +648 -0
  443. claude_mpm/skills/bundled/tauri/tauri-file-system.md +673 -0
  444. claude_mpm/skills/bundled/tauri/tauri-frontend-integration.md +767 -0
  445. claude_mpm/skills/bundled/tauri/tauri-performance.md +669 -0
  446. claude_mpm/skills/bundled/tauri/tauri-state-management.md +573 -0
  447. claude_mpm/skills/bundled/tauri/tauri-testing.md +384 -0
  448. claude_mpm/skills/bundled/tauri/tauri-window-management.md +628 -0
  449. claude_mpm/skills/bundled/testing/condition-based-waiting/SKILL.md +119 -0
  450. claude_mpm/skills/bundled/testing/condition-based-waiting/references/patterns-and-implementation.md +253 -0
  451. claude_mpm/skills/bundled/testing/test-driven-development/SKILL.md +145 -0
  452. claude_mpm/skills/bundled/testing/test-driven-development/references/anti-patterns.md +543 -0
  453. claude_mpm/skills/bundled/testing/test-driven-development/references/examples.md +741 -0
  454. claude_mpm/skills/bundled/testing/test-driven-development/references/integration.md +470 -0
  455. claude_mpm/skills/bundled/testing/test-driven-development/references/philosophy.md +458 -0
  456. claude_mpm/skills/bundled/testing/test-driven-development/references/workflow.md +639 -0
  457. claude_mpm/skills/bundled/testing/test-quality-inspector/SKILL.md +458 -0
  458. claude_mpm/skills/bundled/testing/test-quality-inspector/examples/example-inspection-report.md +411 -0
  459. claude_mpm/skills/bundled/testing/test-quality-inspector/references/assertion-quality.md +317 -0
  460. claude_mpm/skills/bundled/testing/test-quality-inspector/references/inspection-checklist.md +270 -0
  461. claude_mpm/skills/bundled/testing/test-quality-inspector/references/red-flags.md +436 -0
  462. claude_mpm/skills/bundled/testing/testing-anti-patterns/SKILL.md +140 -0
  463. claude_mpm/skills/bundled/testing/testing-anti-patterns/references/completeness-anti-patterns.md +572 -0
  464. claude_mpm/skills/bundled/testing/testing-anti-patterns/references/core-anti-patterns.md +411 -0
  465. claude_mpm/skills/bundled/testing/testing-anti-patterns/references/detection-guide.md +569 -0
  466. claude_mpm/skills/bundled/testing/testing-anti-patterns/references/tdd-connection.md +695 -0
  467. claude_mpm/skills/bundled/testing/webapp-testing/SKILL.md +184 -0
  468. claude_mpm/skills/bundled/testing/webapp-testing/decision-tree.md +459 -0
  469. claude_mpm/skills/bundled/testing/webapp-testing/playwright-patterns.md +479 -0
  470. claude_mpm/skills/bundled/testing/webapp-testing/reconnaissance-pattern.md +687 -0
  471. claude_mpm/skills/bundled/testing/webapp-testing/server-management.md +758 -0
  472. claude_mpm/skills/bundled/testing/webapp-testing/troubleshooting.md +868 -0
  473. claude_mpm/skills/registry.py +295 -90
  474. claude_mpm/skills/skill_manager.py +98 -3
  475. claude_mpm/templates/.pre-commit-config.yaml +112 -0
  476. claude_mpm/utils/agent_dependency_loader.py +115 -4
  477. claude_mpm/utils/agent_filters.py +1 -1
  478. claude_mpm/utils/migration.py +4 -4
  479. claude_mpm/utils/robust_installer.py +86 -21
  480. claude_mpm-5.6.34.dist-info/METADATA +393 -0
  481. {claude_mpm-5.4.22.dist-info → claude_mpm-5.6.34.dist-info}/RECORD +486 -145
  482. claude_mpm-5.4.22.dist-info/METADATA +0 -996
  483. {claude_mpm-5.4.22.dist-info → claude_mpm-5.6.34.dist-info}/WHEEL +0 -0
  484. {claude_mpm-5.4.22.dist-info → claude_mpm-5.6.34.dist-info}/entry_points.txt +0 -0
  485. {claude_mpm-5.4.22.dist-info → claude_mpm-5.6.34.dist-info}/licenses/LICENSE +0 -0
  486. {claude_mpm-5.4.22.dist-info → claude_mpm-5.6.34.dist-info}/licenses/LICENSE-FAQ.md +0 -0
  487. {claude_mpm-5.4.22.dist-info → claude_mpm-5.6.34.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,884 @@
1
+ """PM Skills Deployer Service - Deploy bundled PM skills to projects.
2
+
3
+ WHY: PM agents require specific framework management skills for proper operation.
4
+ This service manages deployment of bundled PM skills from the claude-mpm
5
+ package to the Claude Code skills directory with version tracking.
6
+
7
+ DESIGN DECISIONS:
8
+ - Deploys from src/claude_mpm/skills/bundled/pm/ to .claude/skills/
9
+ - Skills named mpm-* (framework management skills)
10
+ - Direct deployment (no intermediate .claude-mpm/skills/pm/ step)
11
+ - Uses package-relative paths (works for both installed and dev mode)
12
+ - Supports directory structure: mpm-skill-name/SKILL.md
13
+ - Per-project deployment to .claude/skills/ (Claude Code location)
14
+ - Version tracking via .claude-mpm/pm_skills_registry.yaml
15
+ - Checksum validation for integrity verification
16
+ - Conflict resolution: mpm-* skills from src WIN (overwrite existing)
17
+ - Non-mpm-* skills in .claude/skills/ are untouched (user/git managed)
18
+ - Non-blocking verification (returns warnings, doesn't halt execution)
19
+ - Force flag to redeploy even if versions match
20
+
21
+ ARCHITECTURE:
22
+ 1. Discovery: Find bundled PM skills in package (skills/bundled/pm/)
23
+ 2. Deployment: Copy SKILL.md files to .claude/skills/{name}/SKILL.md
24
+ 3. Conflict Check: Overwrite mpm-* skills, preserve non-mpm-* skills
25
+ 4. Registry: Track deployed versions and checksums
26
+ 5. Verification: Check deployment status (non-blocking)
27
+ 6. Updates: Compare bundled vs deployed versions
28
+
29
+ PATH RESOLUTION:
30
+ - Installed package: Uses __file__ to find skills/bundled/pm/
31
+ - Dev mode fallback: .claude-mpm/templates/ at project root
32
+ - Works correctly in both site-packages and development environments
33
+
34
+ References:
35
+ - Parent Service: src/claude_mpm/services/skills_deployer.py
36
+ - Skills Service: src/claude_mpm/skills/skills_service.py
37
+ """
38
+
39
+ import hashlib
40
+ import shutil
41
+ from dataclasses import dataclass
42
+ from datetime import datetime, timezone
43
+ from pathlib import Path
44
+ from typing import Any, Callable, Dict, List, Optional
45
+
46
+ import yaml
47
+
48
+ from claude_mpm.core.mixins import LoggerMixin
49
+
50
+ # Security constants
51
+ MAX_YAML_SIZE = 10 * 1024 * 1024 # 10MB limit to prevent YAML bombs
52
+
53
+ # Tier 1: Required PM skills that MUST be deployed for PM agent to function properly
54
+ # These are core framework management skills for basic PM operation
55
+ REQUIRED_PM_SKILLS = [
56
+ # Core command-based skills (new consolidated CLI)
57
+ "mpm",
58
+ "mpm-init",
59
+ "mpm-status",
60
+ "mpm-help",
61
+ "mpm-doctor",
62
+ # Legacy framework management skills
63
+ "mpm-git-file-tracking",
64
+ "mpm-pr-workflow",
65
+ "mpm-ticketing-integration",
66
+ "mpm-delegation-patterns",
67
+ "mpm-verification-protocols",
68
+ "mpm-bug-reporting",
69
+ "mpm-teaching-mode",
70
+ "mpm-agent-update-workflow",
71
+ "mpm-circuit-breaker-enforcement",
72
+ "mpm-tool-usage-guide",
73
+ "mpm-session-management",
74
+ ]
75
+
76
+ # Tier 2: Recommended skills (deployed with standard install)
77
+ # These provide enhanced functionality for common workflows
78
+ RECOMMENDED_PM_SKILLS = [
79
+ "mpm-config",
80
+ "mpm-ticket-view",
81
+ "mpm-session-resume",
82
+ "mpm-postmortem",
83
+ ]
84
+
85
+ # Tier 3: Optional skills (deployed with full install)
86
+ # These provide additional features for advanced use cases
87
+ OPTIONAL_PM_SKILLS = [
88
+ "mpm-monitor",
89
+ "mpm-version",
90
+ "mpm-organize",
91
+ ]
92
+
93
+
94
+ @dataclass
95
+ class PMSkillInfo:
96
+ """Information about a deployed PM skill.
97
+
98
+ Attributes:
99
+ name: Skill name (directory/file name)
100
+ version: Skill version from metadata
101
+ deployed_at: ISO timestamp of deployment
102
+ checksum: SHA256 checksum of skill content
103
+ source_path: Original bundled skill path
104
+ deployed_path: Deployed skill path
105
+ """
106
+
107
+ name: str
108
+ version: str
109
+ deployed_at: str
110
+ checksum: str
111
+ source_path: Path
112
+ deployed_path: Path
113
+
114
+
115
+ @dataclass
116
+ class DeploymentResult:
117
+ """Result of skill deployment operation.
118
+
119
+ Attributes:
120
+ success: Whether deployment succeeded
121
+ deployed: List of successfully deployed skill names
122
+ skipped: List of skipped skill names (already deployed)
123
+ errors: List of dicts with 'skill' and 'error' keys
124
+ message: Summary message
125
+ """
126
+
127
+ success: bool
128
+ deployed: List[str]
129
+ skipped: List[str]
130
+ errors: List[Dict[str, str]]
131
+ message: str
132
+
133
+
134
+ @dataclass
135
+ class VerificationResult:
136
+ """Result of skill verification operation.
137
+
138
+ Attributes:
139
+ verified: Whether all skills are properly deployed
140
+ warnings: List of warning messages
141
+ missing_skills: List of missing skill names
142
+ corrupted_skills: List of corrupted skill names (checksum mismatch)
143
+ outdated_skills: List of outdated skill names
144
+ message: Summary message
145
+ skill_count: Total number of deployed skills
146
+ """
147
+
148
+ verified: bool
149
+ warnings: List[str]
150
+ missing_skills: List[str]
151
+ corrupted_skills: List[str]
152
+ outdated_skills: List[str]
153
+ message: str
154
+ skill_count: int = 0
155
+
156
+
157
+ @dataclass
158
+ class UpdateInfo:
159
+ """Information about available skill update.
160
+
161
+ Attributes:
162
+ skill_name: Name of skill with update available
163
+ current_version: Currently deployed version
164
+ new_version: Available bundled version
165
+ checksum_changed: Whether content changed (even if version same)
166
+ """
167
+
168
+ skill_name: str
169
+ current_version: str
170
+ new_version: str
171
+ checksum_changed: bool
172
+
173
+
174
+ class PMSkillsDeployerService(LoggerMixin):
175
+ """Deploy and manage PM skills from bundled sources to projects.
176
+
177
+ This service provides:
178
+ - Discovery of bundled PM skills (mpm-* framework management skills)
179
+ - Deployment to .claude/skills/ (Claude Code location)
180
+ - Conflict resolution (mpm-* skills from src WIN)
181
+ - Version tracking via pm_skills_registry.yaml
182
+ - Checksum validation for integrity
183
+ - Non-blocking verification (warnings only)
184
+ - Update detection and deployment
185
+
186
+ Example:
187
+ >>> deployer = PMSkillsDeployerService()
188
+ >>> result = deployer.deploy_pm_skills(Path("/project/root"))
189
+ >>> print(f"Deployed {len(result.deployed)} skills to .claude/skills/")
190
+ >>>
191
+ >>> verify_result = deployer.verify_pm_skills(Path("/project/root"))
192
+ >>> if not verify_result.verified:
193
+ ... print(f"Warnings: {verify_result.warnings}")
194
+ """
195
+
196
+ REGISTRY_VERSION = "1.0.0"
197
+ REGISTRY_FILENAME = "pm_skills_registry.yaml"
198
+
199
+ def __init__(self) -> None:
200
+ """Initialize PM Skills Deployer Service.
201
+
202
+ Sets up paths for:
203
+ - bundled_pm_skills_path: Source bundled PM skills (skills/bundled/pm/)
204
+ - Deployment paths are project-specific (passed to methods)
205
+ """
206
+ super().__init__()
207
+
208
+ # Bundled PM skills are in the package's skills/bundled/pm/ directory
209
+ # This works for both installed packages and development mode
210
+ package_dir = Path(__file__).resolve().parent.parent # Go up to claude_mpm
211
+ self.bundled_pm_skills_path = package_dir / "skills" / "bundled" / "pm"
212
+
213
+ if not self.bundled_pm_skills_path.exists():
214
+ # Fallback: try .claude-mpm/templates/ at project root for dev mode
215
+ self.project_root = self._find_project_root()
216
+ alt_path = self.project_root / ".claude-mpm" / "templates"
217
+ if alt_path.exists():
218
+ self.bundled_pm_skills_path = alt_path
219
+ self.logger.debug(f"Using dev templates path: {alt_path}")
220
+ else:
221
+ self.logger.warning(
222
+ "PM skills templates path not found (non-critical, uses defaults)"
223
+ )
224
+
225
+ def _find_project_root(self) -> Path:
226
+ """Find project root by traversing up from current file.
227
+
228
+ Returns:
229
+ Path to project root (directory containing .git or pyproject.toml)
230
+ """
231
+ current = Path(__file__).resolve()
232
+
233
+ # Traverse up to find project root markers
234
+ for parent in [current] + list(current.parents):
235
+ if (parent / ".git").exists() or (parent / "pyproject.toml").exists():
236
+ return parent
237
+
238
+ # Fallback to current working directory
239
+ return Path.cwd()
240
+
241
+ def _validate_safe_path(self, base: Path, target: Path) -> bool:
242
+ """Ensure target path is within base directory to prevent path traversal.
243
+
244
+ Args:
245
+ base: Base directory that should contain the target
246
+ target: Target path to validate
247
+
248
+ Returns:
249
+ True if path is safe, False otherwise
250
+ """
251
+ try:
252
+ target.resolve().relative_to(base.resolve())
253
+ return True
254
+ except ValueError:
255
+ return False
256
+
257
+ def _compute_checksum(self, file_path: Path) -> str:
258
+ """Compute SHA256 checksum of file content.
259
+
260
+ Args:
261
+ file_path: Path to file to checksum
262
+
263
+ Returns:
264
+ Hex string of SHA256 checksum
265
+ """
266
+ sha256 = hashlib.sha256()
267
+ try:
268
+ with open(file_path, "rb") as f:
269
+ # Read in 64KB chunks to handle large files
270
+ for chunk in iter(lambda: f.read(65536), b""):
271
+ sha256.update(chunk)
272
+ return sha256.hexdigest()
273
+ except OSError as e:
274
+ self.logger.error(f"Failed to compute checksum for {file_path}: {e}")
275
+ return ""
276
+
277
+ def _get_registry_path(self, project_dir: Path) -> Path:
278
+ """Get path to PM skills registry file.
279
+
280
+ Args:
281
+ project_dir: Project root directory
282
+
283
+ Returns:
284
+ Path to pm_skills_registry.yaml
285
+ """
286
+ return project_dir / ".claude-mpm" / self.REGISTRY_FILENAME
287
+
288
+ def _get_deployment_dir(self, project_dir: Path) -> Path:
289
+ """Get deployment directory for PM skills.
290
+
291
+ Args:
292
+ project_dir: Project root directory
293
+
294
+ Returns:
295
+ Path to .claude/skills/
296
+ """
297
+ return project_dir / ".claude" / "skills"
298
+
299
+ def _load_registry(self, project_dir: Path) -> Dict[str, Any]:
300
+ """Load PM skills registry with security checks.
301
+
302
+ Args:
303
+ project_dir: Project root directory
304
+
305
+ Returns:
306
+ Dict containing registry data, or empty dict if not found/invalid
307
+ """
308
+ registry_path = self._get_registry_path(project_dir)
309
+
310
+ if not registry_path.exists():
311
+ self.logger.debug(f"PM skills registry not found: {registry_path}")
312
+ return {}
313
+
314
+ # Check file size to prevent YAML bomb
315
+ try:
316
+ file_size = registry_path.stat().st_size
317
+ if file_size > MAX_YAML_SIZE:
318
+ self.logger.error(
319
+ f"Registry file too large: {file_size} bytes (max {MAX_YAML_SIZE})"
320
+ )
321
+ return {}
322
+ except OSError as e:
323
+ self.logger.error(f"Failed to stat registry file: {e}")
324
+ return {}
325
+
326
+ try:
327
+ with open(registry_path, encoding="utf-8") as f:
328
+ registry = yaml.safe_load(f)
329
+ if not registry:
330
+ self.logger.warning(f"Empty registry file: {registry_path}")
331
+ return {}
332
+ self.logger.debug(f"Loaded PM skills registry from {registry_path}")
333
+ return registry
334
+ except yaml.YAMLError as e:
335
+ self.logger.error(f"Invalid YAML in registry: {e}")
336
+ return {}
337
+ except OSError as e:
338
+ self.logger.error(f"Failed to read registry file: {e}")
339
+ return {}
340
+
341
+ def _save_registry(self, project_dir: Path, registry: Dict[str, Any]) -> bool:
342
+ """Save PM skills registry to file.
343
+
344
+ Args:
345
+ project_dir: Project root directory
346
+ registry: Registry data to save
347
+
348
+ Returns:
349
+ True if save succeeded, False otherwise
350
+ """
351
+ registry_path = self._get_registry_path(project_dir)
352
+
353
+ try:
354
+ # Ensure parent directory exists
355
+ registry_path.parent.mkdir(parents=True, exist_ok=True)
356
+
357
+ with open(registry_path, "w", encoding="utf-8") as f:
358
+ yaml.safe_dump(
359
+ registry, f, default_flow_style=False, allow_unicode=True
360
+ )
361
+
362
+ self.logger.debug(f"Saved PM skills registry to {registry_path}")
363
+ return True
364
+ except (OSError, yaml.YAMLError) as e:
365
+ self.logger.error(f"Failed to save registry: {e}")
366
+ return False
367
+
368
+ def _discover_bundled_pm_skills(self) -> List[Dict[str, Any]]:
369
+ """Discover all PM skills in bundled templates directory.
370
+
371
+ PM skills follow mpm-skill-name/SKILL.md structure.
372
+
373
+ Returns:
374
+ List of skill dictionaries containing:
375
+ - name: Skill name (directory name, e.g., mpm-git-file-tracking)
376
+ - path: Full path to skill file (SKILL.md)
377
+ - type: File type (always 'md')
378
+ """
379
+ skills = []
380
+
381
+ if not self.bundled_pm_skills_path.exists():
382
+ self.logger.warning(
383
+ f"Bundled PM skills path not found: {self.bundled_pm_skills_path}"
384
+ )
385
+ return skills
386
+
387
+ # Scan for skill directories containing SKILL.md
388
+ for skill_dir in self.bundled_pm_skills_path.iterdir():
389
+ if not skill_dir.is_dir() or skill_dir.name.startswith("."):
390
+ continue
391
+
392
+ # Only process mpm* skills (framework management)
393
+ # Note: Includes both 'mpm' (core skill) and 'mpm-*' (other PM skills)
394
+ if not skill_dir.name.startswith("mpm"):
395
+ self.logger.debug(f"Skipping non-mpm skill: {skill_dir.name}")
396
+ continue
397
+
398
+ skill_file = skill_dir / "SKILL.md"
399
+ if skill_file.exists():
400
+ skills.append(
401
+ {
402
+ "name": skill_dir.name,
403
+ "path": skill_file,
404
+ "type": "md",
405
+ }
406
+ )
407
+
408
+ self.logger.info(f"Discovered {len(skills)} bundled PM skills")
409
+ return skills
410
+
411
+ def _get_skills_for_tier(self, tier: str) -> List[str]:
412
+ """Get list of skills to deploy based on tier.
413
+
414
+ Args:
415
+ tier: Deployment tier - "minimal", "standard", or "full"
416
+
417
+ Returns:
418
+ List of skill names to deploy
419
+
420
+ Raises:
421
+ ValueError: If tier is invalid
422
+ """
423
+ if tier == "minimal":
424
+ return REQUIRED_PM_SKILLS
425
+ if tier == "standard":
426
+ return REQUIRED_PM_SKILLS + RECOMMENDED_PM_SKILLS
427
+ if tier == "full":
428
+ return REQUIRED_PM_SKILLS + RECOMMENDED_PM_SKILLS + OPTIONAL_PM_SKILLS
429
+ raise ValueError(
430
+ f"Invalid tier '{tier}'. Must be 'minimal', 'standard', or 'full'"
431
+ )
432
+
433
+ def deploy_pm_skills(
434
+ self,
435
+ project_dir: Path,
436
+ force: bool = False,
437
+ tier: str = "standard",
438
+ progress_callback: Optional[Callable[[str, int, int], None]] = None,
439
+ ) -> DeploymentResult:
440
+ """Deploy bundled PM skills to project directory with tier-based selection.
441
+
442
+ Copies PM skills from bundled templates to .claude/skills/{name}/SKILL.md
443
+ and updates registry with version and checksum information.
444
+
445
+ Deployment Tiers:
446
+ - "minimal": Only REQUIRED_PM_SKILLS (Tier 1 - core functionality)
447
+ - "standard": REQUIRED_PM_SKILLS + RECOMMENDED_PM_SKILLS (Tier 1+2 - common workflows)
448
+ - "full": All skills (Tier 1+2+3 - advanced features)
449
+
450
+ Conflict Resolution:
451
+ - mpm-* skills from src WIN (overwrite existing)
452
+ - Non-mpm-* skills in .claude/skills/ are untouched
453
+
454
+ Args:
455
+ project_dir: Project root directory
456
+ force: If True, redeploy even if skill already exists
457
+ tier: Deployment tier - "minimal", "standard" (default), or "full"
458
+ progress_callback: Optional callback(skill_name, current, total) for progress
459
+
460
+ Returns:
461
+ DeploymentResult with deployment status and details
462
+
463
+ Example:
464
+ >>> # Standard deployment (Tier 1 + Tier 2)
465
+ >>> result = deployer.deploy_pm_skills(Path("/project"))
466
+ >>> print(f"Deployed: {len(result.deployed)} to .claude/skills/")
467
+ >>>
468
+ >>> # Minimal deployment (Tier 1 only)
469
+ >>> result = deployer.deploy_pm_skills(Path("/project"), tier="minimal")
470
+ >>>
471
+ >>> # Full deployment (all tiers)
472
+ >>> result = deployer.deploy_pm_skills(Path("/project"), tier="full")
473
+ """
474
+ # Get tier-based skill filter
475
+ try:
476
+ tier_skills = self._get_skills_for_tier(tier)
477
+ except ValueError as e:
478
+ return DeploymentResult(
479
+ success=False,
480
+ deployed=[],
481
+ skipped=[],
482
+ errors=[{"skill": "all", "error": str(e)}],
483
+ message=str(e),
484
+ )
485
+
486
+ # Discover all bundled skills, then filter by tier
487
+ all_skills = self._discover_bundled_pm_skills()
488
+ skills = [s for s in all_skills if s["name"] in tier_skills]
489
+
490
+ self.logger.info(
491
+ f"Deploying {len(skills)}/{len(all_skills)} skills for tier '{tier}'"
492
+ )
493
+ deployed = []
494
+ skipped = []
495
+ errors = []
496
+
497
+ if not skills:
498
+ return DeploymentResult(
499
+ success=True,
500
+ deployed=[],
501
+ skipped=[],
502
+ errors=[],
503
+ message="No PM skills found to deploy",
504
+ )
505
+
506
+ # Ensure deployment directory exists
507
+ deployment_dir = self._get_deployment_dir(project_dir)
508
+ deployment_dir.mkdir(parents=True, exist_ok=True)
509
+
510
+ # SECURITY: Validate deployment path
511
+ if not self._validate_safe_path(project_dir, deployment_dir):
512
+ return DeploymentResult(
513
+ success=False,
514
+ deployed=[],
515
+ skipped=[],
516
+ errors=[
517
+ {
518
+ "skill": "all",
519
+ "error": "Path traversal attempt detected in deployment directory",
520
+ }
521
+ ],
522
+ message="Security check failed",
523
+ )
524
+
525
+ # Load existing registry
526
+ registry = self._load_registry(project_dir)
527
+ deployed_skills = registry.get("skills", [])
528
+
529
+ # Create lookup for existing deployments
530
+ existing_deployments = {skill["name"]: skill for skill in deployed_skills}
531
+
532
+ new_deployed_skills = []
533
+ timestamp = datetime.now(tz=timezone.utc).isoformat()
534
+ total_skills = len(skills)
535
+
536
+ for idx, skill in enumerate(skills):
537
+ try:
538
+ skill_name = skill["name"]
539
+ source_path = skill["path"]
540
+
541
+ # Report progress if callback provided
542
+ if progress_callback:
543
+ progress_callback(skill_name, idx + 1, total_skills)
544
+
545
+ # Create skill directory: .claude/skills/{skill_name}/
546
+ skill_dir = deployment_dir / skill_name
547
+ skill_dir.mkdir(parents=True, exist_ok=True)
548
+
549
+ # Target path: .claude/skills/{skill_name}/SKILL.md
550
+ target_path = skill_dir / "SKILL.md"
551
+
552
+ # SECURITY: Validate target path
553
+ if not self._validate_safe_path(deployment_dir, target_path):
554
+ raise ValueError(f"Path traversal attempt detected: {target_path}")
555
+
556
+ # Compute checksum of source
557
+ checksum = self._compute_checksum(source_path)
558
+
559
+ # Check if already deployed
560
+ if skill_name in existing_deployments and not force:
561
+ existing = existing_deployments[skill_name]
562
+ if existing.get("checksum") == checksum:
563
+ skipped.append(skill_name)
564
+ new_deployed_skills.append(existing) # Keep existing entry
565
+ self.logger.debug(
566
+ f"Skipped {skill_name} (already deployed with same checksum)"
567
+ )
568
+ continue
569
+
570
+ # Deploy skill (overwrites if exists - mpm-* skills WIN)
571
+ shutil.copy2(source_path, target_path)
572
+
573
+ # Add to deployed list
574
+ deployed.append(skill_name)
575
+
576
+ # Update registry entry
577
+ skill_entry = {
578
+ "name": skill_name,
579
+ "version": "1.0.0", # PM templates don't have versions yet
580
+ "deployed_at": timestamp,
581
+ "checksum": checksum,
582
+ }
583
+ new_deployed_skills.append(skill_entry)
584
+
585
+ self.logger.debug(f"Deployed PM skill: {skill_name}")
586
+
587
+ except (ValueError, OSError) as e:
588
+ self.logger.error(f"Failed to deploy {skill['name']}: {e}")
589
+ errors.append({"skill": skill["name"], "error": str(e)})
590
+
591
+ # Update registry
592
+ updated_registry = {
593
+ "version": self.REGISTRY_VERSION,
594
+ "deployed_at": timestamp,
595
+ "skills": new_deployed_skills,
596
+ }
597
+
598
+ if not self._save_registry(project_dir, updated_registry):
599
+ errors.append(
600
+ {
601
+ "skill": "registry",
602
+ "error": "Failed to save registry after deployment",
603
+ }
604
+ )
605
+
606
+ success = len(errors) == 0
607
+ message = (
608
+ f"Deployed {len(deployed)} skills (tier: {tier}), skipped {len(skipped)}, "
609
+ f"{len(errors)} errors"
610
+ )
611
+
612
+ self.logger.info(message)
613
+
614
+ return DeploymentResult(
615
+ success=success,
616
+ deployed=deployed,
617
+ skipped=skipped,
618
+ errors=errors,
619
+ message=message,
620
+ )
621
+
622
+ def verify_pm_skills(
623
+ self, project_dir: Path, auto_repair: bool = True
624
+ ) -> VerificationResult:
625
+ """Verify PM skills are properly deployed with enhanced validation.
626
+
627
+ Checks ALL required PM skills for:
628
+ - Existence in deployment directory
629
+ - File integrity (non-empty, valid checksums)
630
+ - Version currency (compared to bundled source)
631
+
632
+ Auto-repair logic:
633
+ - If auto_repair=True (default), automatically deploys missing/corrupted skills
634
+ - Reports what was fixed in the result
635
+
636
+ Args:
637
+ project_dir: Project root directory
638
+ auto_repair: If True, auto-deploy missing/corrupted skills (default: True)
639
+
640
+ Returns:
641
+ VerificationResult with detailed verification status:
642
+ - verified: True if all required skills are deployed and valid
643
+ - missing_skills: List of required skills not deployed
644
+ - corrupted_skills: List of skills with checksum mismatches
645
+ - warnings: List of warning messages
646
+ - skill_count: Total number of deployed skills
647
+
648
+ Example:
649
+ >>> result = deployer.verify_pm_skills(Path("/project"))
650
+ >>> if not result.verified:
651
+ ... print(f"Missing: {result.missing_skills}")
652
+ ... print(f"Corrupted: {result.corrupted_skills}")
653
+ """
654
+ warnings = []
655
+ missing_skills = []
656
+ corrupted_skills = []
657
+ outdated_skills = []
658
+
659
+ # Check if registry exists
660
+ registry = self._load_registry(project_dir)
661
+ deployment_dir = self._get_deployment_dir(project_dir)
662
+ deployed_skills_data = registry.get("skills", []) if registry else []
663
+
664
+ # Build lookup for deployed skills
665
+ deployed_lookup = {skill["name"]: skill for skill in deployed_skills_data}
666
+
667
+ # Check ALL required PM skills
668
+ for required_skill in REQUIRED_PM_SKILLS:
669
+ # Check if skill is in registry
670
+ if required_skill not in deployed_lookup:
671
+ warnings.append(f"Required PM skill missing: {required_skill}")
672
+ missing_skills.append(required_skill)
673
+ continue
674
+
675
+ # Check if skill file exists
676
+ skill_file = deployment_dir / required_skill / "SKILL.md"
677
+ if not skill_file.exists():
678
+ warnings.append(
679
+ f"Required PM skill file missing: {required_skill}/SKILL.md"
680
+ )
681
+ missing_skills.append(required_skill)
682
+ continue
683
+
684
+ # Check if skill file is empty/corrupted
685
+ try:
686
+ file_size = skill_file.stat().st_size
687
+ if file_size == 0:
688
+ warnings.append(
689
+ f"Required PM skill file is empty: {required_skill}/SKILL.md"
690
+ )
691
+ corrupted_skills.append(required_skill)
692
+ continue
693
+ except OSError as e:
694
+ warnings.append(
695
+ f"Cannot read required PM skill file: {required_skill}/SKILL.md - {e}"
696
+ )
697
+ corrupted_skills.append(required_skill)
698
+ continue
699
+
700
+ # Verify checksum
701
+ deployed_skill = deployed_lookup[required_skill]
702
+ current_checksum = self._compute_checksum(skill_file)
703
+ expected_checksum = deployed_skill.get("checksum", "")
704
+
705
+ if current_checksum != expected_checksum:
706
+ warnings.append(
707
+ f"Required PM skill checksum mismatch: {required_skill} (file may be corrupted)"
708
+ )
709
+ corrupted_skills.append(required_skill)
710
+
711
+ # Check for available updates (bundled skills newer than deployed)
712
+ bundled_skills = {s["name"]: s for s in self._discover_bundled_pm_skills()}
713
+ for skill_name, bundled_skill in bundled_skills.items():
714
+ # Skip non-required skills
715
+ if skill_name not in REQUIRED_PM_SKILLS:
716
+ continue
717
+
718
+ # Find corresponding deployed skill
719
+ deployed_skill = deployed_lookup.get(skill_name)
720
+
721
+ if not deployed_skill:
722
+ # Already tracked as missing
723
+ continue
724
+
725
+ # Check if checksums differ
726
+ bundled_checksum = self._compute_checksum(bundled_skill["path"])
727
+ deployed_checksum = deployed_skill.get("checksum", "")
728
+
729
+ if bundled_checksum != deployed_checksum:
730
+ # Don't add to outdated_skills if already in corrupted_skills
731
+ if skill_name not in corrupted_skills:
732
+ warnings.append(f"PM skill update available: {skill_name}")
733
+ outdated_skills.append(skill_name)
734
+
735
+ # Auto-repair if enabled and issues found
736
+ repaired_skills = []
737
+ if auto_repair and (missing_skills or corrupted_skills):
738
+ self.logger.info(
739
+ f"Auto-repairing PM skills: {len(missing_skills)} missing, "
740
+ f"{len(corrupted_skills)} corrupted"
741
+ )
742
+
743
+ # Deploy missing and corrupted skills
744
+ repair_result = self.deploy_pm_skills(project_dir, force=True)
745
+
746
+ if repair_result.success:
747
+ repaired_skills = repair_result.deployed
748
+ self.logger.info(f"Auto-repaired {len(repaired_skills)} PM skills")
749
+
750
+ # Remove repaired skills from missing/corrupted lists
751
+ missing_skills = [s for s in missing_skills if s not in repaired_skills]
752
+ corrupted_skills = [
753
+ s for s in corrupted_skills if s not in repaired_skills
754
+ ]
755
+
756
+ # Update warnings
757
+ if repaired_skills:
758
+ warnings.append(
759
+ f"Auto-repaired {len(repaired_skills)} PM skills: {', '.join(repaired_skills)}"
760
+ )
761
+ else:
762
+ warnings.append(
763
+ f"Auto-repair failed: {len(repair_result.errors)} errors"
764
+ )
765
+ self.logger.error(
766
+ f"Auto-repair failed with errors: {repair_result.errors}"
767
+ )
768
+
769
+ # Determine verification status
770
+ verified = len(missing_skills) == 0 and len(corrupted_skills) == 0
771
+
772
+ # Build message
773
+ if verified:
774
+ if repaired_skills:
775
+ message = f"All PM skills verified (auto-repaired {len(repaired_skills)} skills)"
776
+ else:
777
+ message = "All PM skills verified and up-to-date"
778
+ else:
779
+ issue_count = len(missing_skills) + len(corrupted_skills)
780
+ message = f"{issue_count} PM skill issues found"
781
+
782
+ return VerificationResult(
783
+ verified=verified,
784
+ warnings=warnings,
785
+ missing_skills=missing_skills,
786
+ corrupted_skills=corrupted_skills,
787
+ outdated_skills=outdated_skills,
788
+ message=message,
789
+ skill_count=len(deployed_skills_data),
790
+ )
791
+
792
+ def get_deployed_skills(self, project_dir: Path) -> List[PMSkillInfo]:
793
+ """Get list of deployed PM skills with metadata.
794
+
795
+ Args:
796
+ project_dir: Project root directory
797
+
798
+ Returns:
799
+ List of PMSkillInfo objects for deployed skills
800
+
801
+ Example:
802
+ >>> skills = deployer.get_deployed_skills(Path("/project"))
803
+ >>> for skill in skills:
804
+ ... print(f"{skill.name} v{skill.version} ({skill.deployed_at})")
805
+ """
806
+ registry = self._load_registry(project_dir)
807
+ deployment_dir = self._get_deployment_dir(project_dir)
808
+
809
+ skills = []
810
+ for skill_data in registry.get("skills", []):
811
+ skill_name = skill_data["name"]
812
+ deployed_path = deployment_dir / skill_name / "SKILL.md"
813
+
814
+ # Find source path (may not exist if bundled skills changed)
815
+ source_path = self.bundled_pm_skills_path / skill_name / "SKILL.md"
816
+
817
+ skills.append(
818
+ PMSkillInfo(
819
+ name=skill_name,
820
+ version=skill_data.get("version", "1.0.0"),
821
+ deployed_at=skill_data.get("deployed_at", "unknown"),
822
+ checksum=skill_data.get("checksum", ""),
823
+ source_path=source_path,
824
+ deployed_path=deployed_path,
825
+ )
826
+ )
827
+
828
+ return skills
829
+
830
+ def check_updates_available(self, project_dir: Path) -> List[UpdateInfo]:
831
+ """Check for available PM skill updates.
832
+
833
+ Compares bundled skills against deployed skills to identify updates.
834
+
835
+ Args:
836
+ project_dir: Project root directory
837
+
838
+ Returns:
839
+ List of UpdateInfo objects for skills with updates available
840
+
841
+ Example:
842
+ >>> updates = deployer.check_updates_available(Path("/project"))
843
+ >>> for update in updates:
844
+ ... print(f"{update.skill_name}: {update.current_version} -> {update.new_version}")
845
+ """
846
+ registry = self._load_registry(project_dir)
847
+ deployed_skills = {skill["name"]: skill for skill in registry.get("skills", [])}
848
+
849
+ bundled_skills = self._discover_bundled_pm_skills()
850
+
851
+ updates = []
852
+ for bundled_skill in bundled_skills:
853
+ skill_name = bundled_skill["name"]
854
+
855
+ # Compute bundled checksum
856
+ bundled_checksum = self._compute_checksum(bundled_skill["path"])
857
+
858
+ if skill_name not in deployed_skills:
859
+ # New skill available
860
+ updates.append(
861
+ UpdateInfo(
862
+ skill_name=skill_name,
863
+ current_version="not deployed",
864
+ new_version="1.0.0",
865
+ checksum_changed=True,
866
+ )
867
+ )
868
+ continue
869
+
870
+ # Check if checksum differs
871
+ deployed_skill = deployed_skills[skill_name]
872
+ deployed_checksum = deployed_skill.get("checksum", "")
873
+
874
+ if bundled_checksum != deployed_checksum:
875
+ updates.append(
876
+ UpdateInfo(
877
+ skill_name=skill_name,
878
+ current_version=deployed_skill.get("version", "1.0.0"),
879
+ new_version="1.0.0",
880
+ checksum_changed=True,
881
+ )
882
+ )
883
+
884
+ return updates