claude-mpm 5.4.85__py3-none-any.whl → 5.6.1__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 (254) 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 +101 -703
  5. claude_mpm/agents/WORKFLOW.md +2 -0
  6. claude_mpm/agents/templates/circuit-breakers.md +26 -17
  7. claude_mpm/cli/commands/autotodos.py +566 -0
  8. claude_mpm/cli/commands/commander.py +46 -0
  9. claude_mpm/cli/commands/hook_errors.py +60 -60
  10. claude_mpm/cli/commands/monitor.py +2 -2
  11. claude_mpm/cli/commands/mpm_init/core.py +2 -2
  12. claude_mpm/cli/commands/run.py +35 -3
  13. claude_mpm/cli/executor.py +119 -16
  14. claude_mpm/cli/parsers/base_parser.py +71 -1
  15. claude_mpm/cli/parsers/commander_parser.py +83 -0
  16. claude_mpm/cli/parsers/run_parser.py +10 -0
  17. claude_mpm/cli/startup.py +54 -16
  18. claude_mpm/cli/startup_display.py +72 -5
  19. claude_mpm/cli/startup_logging.py +2 -2
  20. claude_mpm/cli/utils.py +7 -3
  21. claude_mpm/commander/__init__.py +72 -0
  22. claude_mpm/commander/adapters/__init__.py +31 -0
  23. claude_mpm/commander/adapters/base.py +191 -0
  24. claude_mpm/commander/adapters/claude_code.py +361 -0
  25. claude_mpm/commander/adapters/communication.py +366 -0
  26. claude_mpm/commander/api/__init__.py +16 -0
  27. claude_mpm/commander/api/app.py +105 -0
  28. claude_mpm/commander/api/errors.py +112 -0
  29. claude_mpm/commander/api/routes/__init__.py +8 -0
  30. claude_mpm/commander/api/routes/events.py +184 -0
  31. claude_mpm/commander/api/routes/inbox.py +171 -0
  32. claude_mpm/commander/api/routes/messages.py +148 -0
  33. claude_mpm/commander/api/routes/projects.py +271 -0
  34. claude_mpm/commander/api/routes/sessions.py +215 -0
  35. claude_mpm/commander/api/routes/work.py +260 -0
  36. claude_mpm/commander/api/schemas.py +182 -0
  37. claude_mpm/commander/chat/__init__.py +7 -0
  38. claude_mpm/commander/chat/cli.py +107 -0
  39. claude_mpm/commander/chat/commands.py +96 -0
  40. claude_mpm/commander/chat/repl.py +310 -0
  41. claude_mpm/commander/config.py +49 -0
  42. claude_mpm/commander/config_loader.py +115 -0
  43. claude_mpm/commander/daemon.py +398 -0
  44. claude_mpm/commander/events/__init__.py +26 -0
  45. claude_mpm/commander/events/manager.py +332 -0
  46. claude_mpm/commander/frameworks/__init__.py +12 -0
  47. claude_mpm/commander/frameworks/base.py +143 -0
  48. claude_mpm/commander/frameworks/claude_code.py +58 -0
  49. claude_mpm/commander/frameworks/mpm.py +62 -0
  50. claude_mpm/commander/inbox/__init__.py +16 -0
  51. claude_mpm/commander/inbox/dedup.py +128 -0
  52. claude_mpm/commander/inbox/inbox.py +224 -0
  53. claude_mpm/commander/inbox/models.py +70 -0
  54. claude_mpm/commander/instance_manager.py +337 -0
  55. claude_mpm/commander/llm/__init__.py +6 -0
  56. claude_mpm/commander/llm/openrouter_client.py +167 -0
  57. claude_mpm/commander/llm/summarizer.py +70 -0
  58. claude_mpm/commander/models/__init__.py +18 -0
  59. claude_mpm/commander/models/events.py +121 -0
  60. claude_mpm/commander/models/project.py +162 -0
  61. claude_mpm/commander/models/work.py +214 -0
  62. claude_mpm/commander/parsing/__init__.py +20 -0
  63. claude_mpm/commander/parsing/extractor.py +132 -0
  64. claude_mpm/commander/parsing/output_parser.py +270 -0
  65. claude_mpm/commander/parsing/patterns.py +100 -0
  66. claude_mpm/commander/persistence/__init__.py +11 -0
  67. claude_mpm/commander/persistence/event_store.py +274 -0
  68. claude_mpm/commander/persistence/state_store.py +309 -0
  69. claude_mpm/commander/persistence/work_store.py +164 -0
  70. claude_mpm/commander/polling/__init__.py +13 -0
  71. claude_mpm/commander/polling/event_detector.py +104 -0
  72. claude_mpm/commander/polling/output_buffer.py +49 -0
  73. claude_mpm/commander/polling/output_poller.py +153 -0
  74. claude_mpm/commander/project_session.py +268 -0
  75. claude_mpm/commander/proxy/__init__.py +12 -0
  76. claude_mpm/commander/proxy/formatter.py +89 -0
  77. claude_mpm/commander/proxy/output_handler.py +191 -0
  78. claude_mpm/commander/proxy/relay.py +155 -0
  79. claude_mpm/commander/registry.py +404 -0
  80. claude_mpm/commander/runtime/__init__.py +10 -0
  81. claude_mpm/commander/runtime/executor.py +191 -0
  82. claude_mpm/commander/runtime/monitor.py +316 -0
  83. claude_mpm/commander/session/__init__.py +6 -0
  84. claude_mpm/commander/session/context.py +81 -0
  85. claude_mpm/commander/session/manager.py +59 -0
  86. claude_mpm/commander/tmux_orchestrator.py +361 -0
  87. claude_mpm/commander/web/__init__.py +1 -0
  88. claude_mpm/commander/work/__init__.py +30 -0
  89. claude_mpm/commander/work/executor.py +189 -0
  90. claude_mpm/commander/work/queue.py +405 -0
  91. claude_mpm/commander/workflow/__init__.py +27 -0
  92. claude_mpm/commander/workflow/event_handler.py +219 -0
  93. claude_mpm/commander/workflow/notifier.py +146 -0
  94. claude_mpm/commands/mpm-config.md +8 -0
  95. claude_mpm/commands/mpm-doctor.md +8 -0
  96. claude_mpm/commands/mpm-help.md +8 -0
  97. claude_mpm/commands/mpm-init.md +8 -0
  98. claude_mpm/commands/mpm-monitor.md +8 -0
  99. claude_mpm/commands/mpm-organize.md +8 -0
  100. claude_mpm/commands/mpm-postmortem.md +8 -0
  101. claude_mpm/commands/mpm-session-resume.md +9 -1
  102. claude_mpm/commands/mpm-status.md +8 -0
  103. claude_mpm/commands/mpm-ticket-view.md +8 -0
  104. claude_mpm/commands/mpm-version.md +8 -0
  105. claude_mpm/commands/mpm.md +8 -0
  106. claude_mpm/config/agent_presets.py +8 -7
  107. claude_mpm/core/config.py +5 -0
  108. claude_mpm/core/hook_manager.py +51 -3
  109. claude_mpm/core/logger.py +10 -7
  110. claude_mpm/core/logging_utils.py +4 -2
  111. claude_mpm/core/output_style_manager.py +15 -5
  112. claude_mpm/core/unified_config.py +10 -6
  113. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.C33zOoyM.css +1 -0
  114. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.CW1J-YuA.css +1 -0
  115. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Cs_tUR18.js → 1WZnGYqX.js} +1 -1
  116. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CDuw-vjf.js → 67pF3qNn.js} +1 -1
  117. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{bTOqqlTd.js → 6RxdMKe4.js} +1 -1
  118. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DwBR2MJi.js → 8cZrfX0h.js} +1 -1
  119. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{ZGh7QtNv.js → 9a6T2nm-.js} +1 -1
  120. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{D9lljYKQ.js → B443AUzu.js} +1 -1
  121. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{RJiighC3.js → B8AwtY2H.js} +1 -1
  122. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{uuIeMWc-.js → BF15LAsF.js} +1 -1
  123. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{D3k0OPJN.js → BRcwIQNr.js} +1 -1
  124. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CyWMqx4W.js → BV6nKitt.js} +1 -1
  125. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CiIAseT4.js → BViJ8lZt.js} +5 -5
  126. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CBBdVcY8.js → BcQ-Q0FE.js} +1 -1
  127. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BovzEFCE.js → Bpyvgze_.js} +1 -1
  128. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BzTRqg-z.js +1 -0
  129. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C0Fr8dve.js +1 -0
  130. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{eNVUfhuA.js → C3rbW_a-.js} +1 -1
  131. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{GYwsonyD.js → C8WYN38h.js} +1 -1
  132. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BIF9m_hv.js → C9I8FlXH.js} +1 -1
  133. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B0uc0UOD.js → CIQcWgO2.js} +3 -3
  134. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Be7GpZd6.js → CIctN7YN.js} +1 -1
  135. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Bh0LDWpI.js → CKrS_JZW.js} +2 -2
  136. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DUrLdbGD.js → CR6P9C4A.js} +1 -1
  137. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B7xVLGWV.js → CRRR9MD_.js} +1 -1
  138. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CRcR2DqT.js +334 -0
  139. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Dhb8PKl3.js → CSXtMOf0.js} +1 -1
  140. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BPYeabCQ.js → CT-sbxSk.js} +1 -1
  141. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{sQeU3Y1z.js → CWm6DJsp.js} +1 -1
  142. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CnA0NrzZ.js → CpqQ1Kzn.js} +1 -1
  143. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C4B-KCzX.js → D2nGpDRe.js} +1 -1
  144. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DGkLK5U1.js → D9iCMida.js} +1 -1
  145. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BofRWZRR.js → D9ykgMoY.js} +1 -1
  146. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DmxopI1J.js → DL2Ldur1.js} +1 -1
  147. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C30mlcqg.js → DPfltzjH.js} +1 -1
  148. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Vzk33B_K.js → DR8nis88.js} +2 -2
  149. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DI7hHRFL.js → DUliQN2b.js} +1 -1
  150. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C4JcI4KD.js → DXlhR01x.js} +1 -1
  151. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{bT1r9zLR.js → D_lyTybS.js} +1 -1
  152. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DZX00Y4g.js → DngoTTgh.js} +1 -1
  153. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CzZX-COe.js → DqkmHtDC.js} +1 -1
  154. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B7RN905-.js → DsDh8EYs.js} +1 -1
  155. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DLVjFsZ3.js → DypDmXgd.js} +1 -1
  156. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{iEWssX7S.js → IPYC-LnN.js} +1 -1
  157. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/JTLiF7dt.js +24 -0
  158. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DaimHw_p.js → JpevfAFt.js} +1 -1
  159. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DY1XQ8fi.js → R8CEIRAd.js} +1 -1
  160. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Dle-35c7.js → Zxy7qc-l.js} +2 -2
  161. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/q9Hm6zAU.js +1 -0
  162. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C_Usid8X.js → qtd3IeO4.js} +2 -2
  163. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CzeYkLYB.js → ulBFON_C.js} +2 -2
  164. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Cfqx1Qun.js → wQVh1CoA.js} +1 -1
  165. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/{app.D6-I5TpK.js → app.Dr7t0z2J.js} +2 -2
  166. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.BGhZHUS3.js +1 -0
  167. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/{0.m1gL8KXf.js → 0.RgBboRvH.js} +1 -1
  168. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/{1.CgNOuw-d.js → 1.DG-KkbDf.js} +1 -1
  169. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.D_jnf-x6.js +1 -0
  170. claude_mpm/dashboard/static/svelte-build/_app/version.json +1 -1
  171. claude_mpm/dashboard/static/svelte-build/index.html +9 -9
  172. claude_mpm/experimental/cli_enhancements.py +2 -1
  173. claude_mpm/hooks/claude_hooks/INTEGRATION_EXAMPLE.md +243 -0
  174. claude_mpm/hooks/claude_hooks/README_AUTO_PAUSE.md +403 -0
  175. claude_mpm/hooks/claude_hooks/auto_pause_handler.py +486 -0
  176. claude_mpm/hooks/claude_hooks/event_handlers.py +250 -11
  177. claude_mpm/hooks/claude_hooks/hook_handler.py +106 -89
  178. claude_mpm/hooks/claude_hooks/hook_wrapper.sh +6 -11
  179. claude_mpm/hooks/claude_hooks/installer.py +69 -5
  180. claude_mpm/hooks/claude_hooks/response_tracking.py +3 -1
  181. claude_mpm/hooks/claude_hooks/services/connection_manager.py +20 -0
  182. claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +14 -77
  183. claude_mpm/hooks/claude_hooks/services/subagent_processor.py +30 -6
  184. claude_mpm/hooks/session_resume_hook.py +85 -1
  185. claude_mpm/init.py +1 -1
  186. claude_mpm/scripts/claude-hook-handler.sh +36 -10
  187. claude_mpm/services/agents/agent_recommendation_service.py +8 -8
  188. claude_mpm/services/agents/cache_git_manager.py +1 -1
  189. claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +3 -0
  190. claude_mpm/services/agents/loading/framework_agent_loader.py +75 -2
  191. claude_mpm/services/cli/__init__.py +3 -0
  192. claude_mpm/services/cli/incremental_pause_manager.py +561 -0
  193. claude_mpm/services/cli/session_resume_helper.py +10 -2
  194. claude_mpm/services/delegation_detector.py +175 -0
  195. claude_mpm/services/diagnostics/checks/agent_sources_check.py +30 -0
  196. claude_mpm/services/diagnostics/checks/configuration_check.py +24 -0
  197. claude_mpm/services/diagnostics/checks/installation_check.py +22 -0
  198. claude_mpm/services/diagnostics/checks/mcp_services_check.py +23 -0
  199. claude_mpm/services/diagnostics/doctor_reporter.py +31 -1
  200. claude_mpm/services/diagnostics/models.py +14 -1
  201. claude_mpm/services/event_log.py +325 -0
  202. claude_mpm/services/infrastructure/__init__.py +4 -0
  203. claude_mpm/services/infrastructure/context_usage_tracker.py +291 -0
  204. claude_mpm/services/infrastructure/resume_log_generator.py +24 -5
  205. claude_mpm/services/monitor/daemon_manager.py +15 -4
  206. claude_mpm/services/monitor/management/lifecycle.py +8 -2
  207. claude_mpm/services/monitor/server.py +106 -16
  208. claude_mpm/services/pm_skills_deployer.py +259 -87
  209. claude_mpm/services/skills/git_skill_source_manager.py +51 -2
  210. claude_mpm/services/skills/selective_skill_deployer.py +114 -16
  211. claude_mpm/services/skills/skill_discovery_service.py +57 -3
  212. claude_mpm/services/socketio/handlers/hook.py +14 -7
  213. claude_mpm/services/socketio/server/main.py +12 -4
  214. claude_mpm/skills/bundled/pm/mpm/SKILL.md +38 -0
  215. claude_mpm/skills/bundled/pm/mpm-agent-update-workflow/SKILL.md +75 -0
  216. claude_mpm/skills/bundled/pm/mpm-circuit-breaker-enforcement/SKILL.md +476 -0
  217. claude_mpm/skills/bundled/pm/mpm-config/SKILL.md +29 -0
  218. claude_mpm/skills/bundled/pm/mpm-doctor/SKILL.md +53 -0
  219. claude_mpm/skills/bundled/pm/mpm-help/SKILL.md +35 -0
  220. claude_mpm/skills/bundled/pm/mpm-init/SKILL.md +125 -0
  221. claude_mpm/skills/bundled/pm/mpm-monitor/SKILL.md +32 -0
  222. claude_mpm/skills/bundled/pm/mpm-organize/SKILL.md +121 -0
  223. claude_mpm/skills/bundled/pm/mpm-postmortem/SKILL.md +22 -0
  224. claude_mpm/skills/bundled/pm/mpm-session-management/SKILL.md +312 -0
  225. claude_mpm/skills/bundled/pm/mpm-session-resume/SKILL.md +31 -0
  226. claude_mpm/skills/bundled/pm/mpm-status/SKILL.md +37 -0
  227. claude_mpm/skills/bundled/pm/{pm-teaching-mode → mpm-teaching-mode}/SKILL.md +2 -2
  228. claude_mpm/skills/bundled/pm/mpm-ticket-view/SKILL.md +110 -0
  229. claude_mpm/skills/bundled/pm/mpm-tool-usage-guide/SKILL.md +386 -0
  230. claude_mpm/skills/bundled/pm/mpm-version/SKILL.md +21 -0
  231. claude_mpm/skills/skill_manager.py +4 -4
  232. claude_mpm-5.6.1.dist-info/METADATA +391 -0
  233. {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.1.dist-info}/RECORD +244 -145
  234. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.DWzvg0-y.css +0 -1
  235. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.ThTw9_ym.css +0 -1
  236. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/4TdZjIqw.js +0 -1
  237. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/5shd3_w0.js +0 -24
  238. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BKjSRqUr.js +0 -1
  239. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Da0KfYnO.js +0 -1
  240. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Dfy6j1xT.js +0 -323
  241. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.NWzMBYRp.js +0 -1
  242. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.C0GcWctS.js +0 -1
  243. claude_mpm-5.4.85.dist-info/METADATA +0 -1023
  244. /claude_mpm/skills/bundled/pm/{pm-bug-reporting/pm-bug-reporting.md → mpm-bug-reporting/SKILL.md} +0 -0
  245. /claude_mpm/skills/bundled/pm/{pm-delegation-patterns → mpm-delegation-patterns}/SKILL.md +0 -0
  246. /claude_mpm/skills/bundled/pm/{pm-git-file-tracking → mpm-git-file-tracking}/SKILL.md +0 -0
  247. /claude_mpm/skills/bundled/pm/{pm-pr-workflow → mpm-pr-workflow}/SKILL.md +0 -0
  248. /claude_mpm/skills/bundled/pm/{pm-ticketing-integration → mpm-ticketing-integration}/SKILL.md +0 -0
  249. /claude_mpm/skills/bundled/pm/{pm-verification-protocols → mpm-verification-protocols}/SKILL.md +0 -0
  250. {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.1.dist-info}/WHEEL +0 -0
  251. {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.1.dist-info}/entry_points.txt +0 -0
  252. {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.1.dist-info}/licenses/LICENSE +0 -0
  253. {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.1.dist-info}/licenses/LICENSE-FAQ.md +0 -0
  254. {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.1.dist-info}/top_level.txt +0 -0
@@ -1,27 +1,30 @@
1
1
  """PM Skills Deployer Service - Deploy bundled PM skills to projects.
2
2
 
3
- WHY: PM agents require specific templates and skills for proper operation.
3
+ WHY: PM agents require specific framework management skills for proper operation.
4
4
  This service manages deployment of bundled PM skills from the claude-mpm
5
- package to individual project .claude-mpm directories with version tracking.
5
+ package to the Claude Code skills directory with version tracking.
6
6
 
7
7
  DESIGN DECISIONS:
8
- - Deploys from src/claude_mpm/skills/bundled/pm/ to .claude-mpm/skills/pm/
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)
9
11
  - Uses package-relative paths (works for both installed and dev mode)
10
- - Supports two skill formats:
11
- 1. Directory structure: pm-skill-name/SKILL.md (new format)
12
- 2. Flat files: skill-name.md (legacy format in .claude-mpm/templates/)
13
- - Per-project deployment (NOT global like Claude Code skills)
12
+ - Supports directory structure: mpm-skill-name/SKILL.md
13
+ - Per-project deployment to .claude/skills/ (Claude Code location)
14
14
  - Version tracking via .claude-mpm/pm_skills_registry.yaml
15
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)
16
18
  - Non-blocking verification (returns warnings, doesn't halt execution)
17
19
  - Force flag to redeploy even if versions match
18
20
 
19
21
  ARCHITECTURE:
20
22
  1. Discovery: Find bundled PM skills in package (skills/bundled/pm/)
21
- 2. Deployment: Copy SKILL.md files to .claude-mpm/skills/pm/{name}.md
22
- 3. Registry: Track deployed versions and checksums
23
- 4. Verification: Check deployment status (non-blocking)
24
- 5. Updates: Compare bundled vs deployed versions
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
25
28
 
26
29
  PATH RESOLUTION:
27
30
  - Installed package: Uses __file__ to find skills/bundled/pm/
@@ -47,6 +50,46 @@ from claude_mpm.core.mixins import LoggerMixin
47
50
  # Security constants
48
51
  MAX_YAML_SIZE = 10 * 1024 * 1024 # 10MB limit to prevent YAML bombs
49
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
+
50
93
 
51
94
  @dataclass
52
95
  class PMSkillInfo:
@@ -96,6 +139,7 @@ class VerificationResult:
96
139
  verified: Whether all skills are properly deployed
97
140
  warnings: List of warning messages
98
141
  missing_skills: List of missing skill names
142
+ corrupted_skills: List of corrupted skill names (checksum mismatch)
99
143
  outdated_skills: List of outdated skill names
100
144
  message: Summary message
101
145
  skill_count: Total number of deployed skills
@@ -104,6 +148,7 @@ class VerificationResult:
104
148
  verified: bool
105
149
  warnings: List[str]
106
150
  missing_skills: List[str]
151
+ corrupted_skills: List[str]
107
152
  outdated_skills: List[str]
108
153
  message: str
109
154
  skill_count: int = 0
@@ -130,8 +175,9 @@ class PMSkillsDeployerService(LoggerMixin):
130
175
  """Deploy and manage PM skills from bundled sources to projects.
131
176
 
132
177
  This service provides:
133
- - Discovery of bundled PM skills (templates)
134
- - Deployment to .claude-mpm/skills/pm/
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)
135
181
  - Version tracking via pm_skills_registry.yaml
136
182
  - Checksum validation for integrity
137
183
  - Non-blocking verification (warnings only)
@@ -140,7 +186,7 @@ class PMSkillsDeployerService(LoggerMixin):
140
186
  Example:
141
187
  >>> deployer = PMSkillsDeployerService()
142
188
  >>> result = deployer.deploy_pm_skills(Path("/project/root"))
143
- >>> print(f"Deployed {len(result.deployed)} skills")
189
+ >>> print(f"Deployed {len(result.deployed)} skills to .claude/skills/")
144
190
  >>>
145
191
  >>> verify_result = deployer.verify_pm_skills(Path("/project/root"))
146
192
  >>> if not verify_result.verified:
@@ -246,9 +292,9 @@ class PMSkillsDeployerService(LoggerMixin):
246
292
  project_dir: Project root directory
247
293
 
248
294
  Returns:
249
- Path to .claude-mpm/skills/pm/
295
+ Path to .claude/skills/
250
296
  """
251
- return project_dir / ".claude-mpm" / "skills" / "pm"
297
+ return project_dir / ".claude" / "skills"
252
298
 
253
299
  def _load_registry(self, project_dir: Path) -> Dict[str, Any]:
254
300
  """Load PM skills registry with security checks.
@@ -322,14 +368,12 @@ class PMSkillsDeployerService(LoggerMixin):
322
368
  def _discover_bundled_pm_skills(self) -> List[Dict[str, Any]]:
323
369
  """Discover all PM skills in bundled templates directory.
324
370
 
325
- PM skills can be in two formats:
326
- 1. Directory structure: pm-skill-name/SKILL.md (new format)
327
- 2. Flat files: skill-name.md (legacy format for .claude-mpm/templates/)
371
+ PM skills follow mpm-skill-name/SKILL.md structure.
328
372
 
329
373
  Returns:
330
374
  List of skill dictionaries containing:
331
- - name: Skill name (directory/filename without extension)
332
- - path: Full path to skill file (SKILL.md or .md file)
375
+ - name: Skill name (directory name, e.g., mpm-git-file-tracking)
376
+ - path: Full path to skill file (SKILL.md)
333
377
  - type: File type (always 'md')
334
378
  """
335
379
  skills = []
@@ -340,11 +384,16 @@ class PMSkillsDeployerService(LoggerMixin):
340
384
  )
341
385
  return skills
342
386
 
343
- # Scan for skill directories containing SKILL.md (new format)
387
+ # Scan for skill directories containing SKILL.md
344
388
  for skill_dir in self.bundled_pm_skills_path.iterdir():
345
389
  if not skill_dir.is_dir() or skill_dir.name.startswith("."):
346
390
  continue
347
391
 
392
+ # Only process mpm-* skills (framework management)
393
+ if not skill_dir.name.startswith("mpm-"):
394
+ self.logger.debug(f"Skipping non-mpm skill: {skill_dir.name}")
395
+ continue
396
+
348
397
  skill_file = skill_dir / "SKILL.md"
349
398
  if skill_file.exists():
350
399
  skills.append(
@@ -355,46 +404,91 @@ class PMSkillsDeployerService(LoggerMixin):
355
404
  }
356
405
  )
357
406
 
358
- # Fallback: Scan for .md files directly (legacy format)
359
- for skill_file in self.bundled_pm_skills_path.glob("*.md"):
360
- if skill_file.name.startswith("."):
361
- continue
362
-
363
- skills.append(
364
- {
365
- "name": skill_file.stem,
366
- "path": skill_file,
367
- "type": "md",
368
- }
369
- )
370
-
371
407
  self.logger.info(f"Discovered {len(skills)} bundled PM skills")
372
408
  return skills
373
409
 
410
+ def _get_skills_for_tier(self, tier: str) -> List[str]:
411
+ """Get list of skills to deploy based on tier.
412
+
413
+ Args:
414
+ tier: Deployment tier - "minimal", "standard", or "full"
415
+
416
+ Returns:
417
+ List of skill names to deploy
418
+
419
+ Raises:
420
+ ValueError: If tier is invalid
421
+ """
422
+ if tier == "minimal":
423
+ return REQUIRED_PM_SKILLS
424
+ if tier == "standard":
425
+ return REQUIRED_PM_SKILLS + RECOMMENDED_PM_SKILLS
426
+ if tier == "full":
427
+ return REQUIRED_PM_SKILLS + RECOMMENDED_PM_SKILLS + OPTIONAL_PM_SKILLS
428
+ raise ValueError(
429
+ f"Invalid tier '{tier}'. Must be 'minimal', 'standard', or 'full'"
430
+ )
431
+
374
432
  def deploy_pm_skills(
375
433
  self,
376
434
  project_dir: Path,
377
435
  force: bool = False,
436
+ tier: str = "standard",
378
437
  progress_callback: Optional[Callable[[str, int, int], None]] = None,
379
438
  ) -> DeploymentResult:
380
- """Deploy bundled PM skills to project directory.
439
+ """Deploy bundled PM skills to project directory with tier-based selection.
381
440
 
382
- Copies PM skills from bundled templates to .claude-mpm/skills/pm/
441
+ Copies PM skills from bundled templates to .claude/skills/{name}/SKILL.md
383
442
  and updates registry with version and checksum information.
384
443
 
444
+ Deployment Tiers:
445
+ - "minimal": Only REQUIRED_PM_SKILLS (Tier 1 - core functionality)
446
+ - "standard": REQUIRED_PM_SKILLS + RECOMMENDED_PM_SKILLS (Tier 1+2 - common workflows)
447
+ - "full": All skills (Tier 1+2+3 - advanced features)
448
+
449
+ Conflict Resolution:
450
+ - mpm-* skills from src WIN (overwrite existing)
451
+ - Non-mpm-* skills in .claude/skills/ are untouched
452
+
385
453
  Args:
386
454
  project_dir: Project root directory
387
455
  force: If True, redeploy even if skill already exists
456
+ tier: Deployment tier - "minimal", "standard" (default), or "full"
388
457
  progress_callback: Optional callback(skill_name, current, total) for progress
389
458
 
390
459
  Returns:
391
460
  DeploymentResult with deployment status and details
392
461
 
393
462
  Example:
394
- >>> result = deployer.deploy_pm_skills(Path("/project"), force=True)
395
- >>> print(f"Deployed: {len(result.deployed)}")
463
+ >>> # Standard deployment (Tier 1 + Tier 2)
464
+ >>> result = deployer.deploy_pm_skills(Path("/project"))
465
+ >>> print(f"Deployed: {len(result.deployed)} to .claude/skills/")
466
+ >>>
467
+ >>> # Minimal deployment (Tier 1 only)
468
+ >>> result = deployer.deploy_pm_skills(Path("/project"), tier="minimal")
469
+ >>>
470
+ >>> # Full deployment (all tiers)
471
+ >>> result = deployer.deploy_pm_skills(Path("/project"), tier="full")
396
472
  """
397
- skills = self._discover_bundled_pm_skills()
473
+ # Get tier-based skill filter
474
+ try:
475
+ tier_skills = self._get_skills_for_tier(tier)
476
+ except ValueError as e:
477
+ return DeploymentResult(
478
+ success=False,
479
+ deployed=[],
480
+ skipped=[],
481
+ errors=[{"skill": "all", "error": str(e)}],
482
+ message=str(e),
483
+ )
484
+
485
+ # Discover all bundled skills, then filter by tier
486
+ all_skills = self._discover_bundled_pm_skills()
487
+ skills = [s for s in all_skills if s["name"] in tier_skills]
488
+
489
+ self.logger.info(
490
+ f"Deploying {len(skills)}/{len(all_skills)} skills for tier '{tier}'"
491
+ )
398
492
  deployed = []
399
493
  skipped = []
400
494
  errors = []
@@ -447,8 +541,12 @@ class PMSkillsDeployerService(LoggerMixin):
447
541
  if progress_callback:
448
542
  progress_callback(skill_name, idx + 1, total_skills)
449
543
 
450
- # Use skill name for target file (e.g., pm-delegation-patterns.md)
451
- target_path = deployment_dir / f"{skill_name}.md"
544
+ # Create skill directory: .claude/skills/{skill_name}/
545
+ skill_dir = deployment_dir / skill_name
546
+ skill_dir.mkdir(parents=True, exist_ok=True)
547
+
548
+ # Target path: .claude/skills/{skill_name}/SKILL.md
549
+ target_path = skill_dir / "SKILL.md"
452
550
 
453
551
  # SECURITY: Validate target path
454
552
  if not self._validate_safe_path(deployment_dir, target_path):
@@ -468,7 +566,7 @@ class PMSkillsDeployerService(LoggerMixin):
468
566
  )
469
567
  continue
470
568
 
471
- # Deploy skill
569
+ # Deploy skill (overwrites if exists - mpm-* skills WIN)
472
570
  shutil.copy2(source_path, target_path)
473
571
 
474
572
  # Add to deployed list
@@ -506,7 +604,7 @@ class PMSkillsDeployerService(LoggerMixin):
506
604
 
507
605
  success = len(errors) == 0
508
606
  message = (
509
- f"Deployed {len(deployed)} skills, skipped {len(skipped)}, "
607
+ f"Deployed {len(deployed)} skills (tier: {tier}), skipped {len(skipped)}, "
510
608
  f"{len(errors)} errors"
511
609
  )
512
610
 
@@ -520,76 +618,107 @@ class PMSkillsDeployerService(LoggerMixin):
520
618
  message=message,
521
619
  )
522
620
 
523
- def verify_pm_skills(self, project_dir: Path) -> VerificationResult:
524
- """Verify PM skills are properly deployed (non-blocking).
621
+ def verify_pm_skills(
622
+ self, project_dir: Path, auto_repair: bool = True
623
+ ) -> VerificationResult:
624
+ """Verify PM skills are properly deployed with enhanced validation.
525
625
 
526
- Checks deployment status and returns warnings without halting execution.
527
- This allows graceful degradation if PM skills are missing.
626
+ Checks ALL required PM skills for:
627
+ - Existence in deployment directory
628
+ - File integrity (non-empty, valid checksums)
629
+ - Version currency (compared to bundled source)
630
+
631
+ Auto-repair logic:
632
+ - If auto_repair=True (default), automatically deploys missing/corrupted skills
633
+ - Reports what was fixed in the result
528
634
 
529
635
  Args:
530
636
  project_dir: Project root directory
637
+ auto_repair: If True, auto-deploy missing/corrupted skills (default: True)
531
638
 
532
639
  Returns:
533
- VerificationResult with verification status and warnings
640
+ VerificationResult with detailed verification status:
641
+ - verified: True if all required skills are deployed and valid
642
+ - missing_skills: List of required skills not deployed
643
+ - corrupted_skills: List of skills with checksum mismatches
644
+ - warnings: List of warning messages
645
+ - skill_count: Total number of deployed skills
534
646
 
535
647
  Example:
536
648
  >>> result = deployer.verify_pm_skills(Path("/project"))
537
649
  >>> if not result.verified:
538
- ... for warning in result.warnings:
539
- ... print(f"WARNING: {warning}")
650
+ ... print(f"Missing: {result.missing_skills}")
651
+ ... print(f"Corrupted: {result.corrupted_skills}")
540
652
  """
541
653
  warnings = []
542
654
  missing_skills = []
655
+ corrupted_skills = []
543
656
  outdated_skills = []
544
657
 
545
658
  # Check if registry exists
546
659
  registry = self._load_registry(project_dir)
547
- if not registry:
548
- warnings.append("PM skills registry not found or invalid")
549
- missing_skills.append("all")
550
- return VerificationResult(
551
- verified=False,
552
- warnings=warnings,
553
- missing_skills=missing_skills,
554
- outdated_skills=outdated_skills,
555
- message="PM skills not deployed. Run 'claude-mpm init' to deploy.",
556
- skill_count=0,
557
- )
558
-
559
- # Check each registered skill exists
560
660
  deployment_dir = self._get_deployment_dir(project_dir)
561
- deployed_skills = registry.get("skills", [])
661
+ deployed_skills_data = registry.get("skills", []) if registry else []
562
662
 
563
- for skill in deployed_skills:
564
- skill_name = skill["name"]
565
- skill_file = deployment_dir / f"{skill_name}.md"
663
+ # Build lookup for deployed skills
664
+ deployed_lookup = {skill["name"]: skill for skill in deployed_skills_data}
566
665
 
666
+ # Check ALL required PM skills
667
+ for required_skill in REQUIRED_PM_SKILLS:
668
+ # Check if skill is in registry
669
+ if required_skill not in deployed_lookup:
670
+ warnings.append(f"Required PM skill missing: {required_skill}")
671
+ missing_skills.append(required_skill)
672
+ continue
673
+
674
+ # Check if skill file exists
675
+ skill_file = deployment_dir / required_skill / "SKILL.md"
567
676
  if not skill_file.exists():
568
- warnings.append(f"Deployed skill file missing: {skill_name}")
569
- missing_skills.append(skill_name)
677
+ warnings.append(
678
+ f"Required PM skill file missing: {required_skill}/SKILL.md"
679
+ )
680
+ missing_skills.append(required_skill)
681
+ continue
682
+
683
+ # Check if skill file is empty/corrupted
684
+ try:
685
+ file_size = skill_file.stat().st_size
686
+ if file_size == 0:
687
+ warnings.append(
688
+ f"Required PM skill file is empty: {required_skill}/SKILL.md"
689
+ )
690
+ corrupted_skills.append(required_skill)
691
+ continue
692
+ except OSError as e:
693
+ warnings.append(
694
+ f"Cannot read required PM skill file: {required_skill}/SKILL.md - {e}"
695
+ )
696
+ corrupted_skills.append(required_skill)
570
697
  continue
571
698
 
572
699
  # Verify checksum
700
+ deployed_skill = deployed_lookup[required_skill]
573
701
  current_checksum = self._compute_checksum(skill_file)
574
- expected_checksum = skill.get("checksum", "")
702
+ expected_checksum = deployed_skill.get("checksum", "")
575
703
 
576
704
  if current_checksum != expected_checksum:
577
705
  warnings.append(
578
- f"Skill checksum mismatch: {skill_name} (file may be corrupted)"
706
+ f"Required PM skill checksum mismatch: {required_skill} (file may be corrupted)"
579
707
  )
580
- outdated_skills.append(skill_name)
708
+ corrupted_skills.append(required_skill)
581
709
 
582
- # Check for available updates
710
+ # Check for available updates (bundled skills newer than deployed)
583
711
  bundled_skills = {s["name"]: s for s in self._discover_bundled_pm_skills()}
584
712
  for skill_name, bundled_skill in bundled_skills.items():
713
+ # Skip non-required skills
714
+ if skill_name not in REQUIRED_PM_SKILLS:
715
+ continue
716
+
585
717
  # Find corresponding deployed skill
586
- deployed_skill = next(
587
- (s for s in deployed_skills if s["name"] == skill_name), None
588
- )
718
+ deployed_skill = deployed_lookup.get(skill_name)
589
719
 
590
720
  if not deployed_skill:
591
- warnings.append(f"New PM skill available: {skill_name}")
592
- missing_skills.append(skill_name)
721
+ # Already tracked as missing
593
722
  continue
594
723
 
595
724
  # Check if checksums differ
@@ -597,23 +726,66 @@ class PMSkillsDeployerService(LoggerMixin):
597
726
  deployed_checksum = deployed_skill.get("checksum", "")
598
727
 
599
728
  if bundled_checksum != deployed_checksum:
600
- warnings.append(f"PM skill update available: {skill_name}")
601
- outdated_skills.append(skill_name)
729
+ # Don't add to outdated_skills if already in corrupted_skills
730
+ if skill_name not in corrupted_skills:
731
+ warnings.append(f"PM skill update available: {skill_name}")
732
+ outdated_skills.append(skill_name)
733
+
734
+ # Auto-repair if enabled and issues found
735
+ repaired_skills = []
736
+ if auto_repair and (missing_skills or corrupted_skills):
737
+ self.logger.info(
738
+ f"Auto-repairing PM skills: {len(missing_skills)} missing, "
739
+ f"{len(corrupted_skills)} corrupted"
740
+ )
741
+
742
+ # Deploy missing and corrupted skills
743
+ repair_result = self.deploy_pm_skills(project_dir, force=True)
744
+
745
+ if repair_result.success:
746
+ repaired_skills = repair_result.deployed
747
+ self.logger.info(f"Auto-repaired {len(repaired_skills)} PM skills")
748
+
749
+ # Remove repaired skills from missing/corrupted lists
750
+ missing_skills = [s for s in missing_skills if s not in repaired_skills]
751
+ corrupted_skills = [
752
+ s for s in corrupted_skills if s not in repaired_skills
753
+ ]
754
+
755
+ # Update warnings
756
+ if repaired_skills:
757
+ warnings.append(
758
+ f"Auto-repaired {len(repaired_skills)} PM skills: {', '.join(repaired_skills)}"
759
+ )
760
+ else:
761
+ warnings.append(
762
+ f"Auto-repair failed: {len(repair_result.errors)} errors"
763
+ )
764
+ self.logger.error(
765
+ f"Auto-repair failed with errors: {repair_result.errors}"
766
+ )
602
767
 
603
- verified = len(warnings) == 0
768
+ # Determine verification status
769
+ verified = len(missing_skills) == 0 and len(corrupted_skills) == 0
604
770
 
771
+ # Build message
605
772
  if verified:
606
- message = "All PM skills verified and up-to-date"
773
+ if repaired_skills:
774
+ message = f"All PM skills verified (auto-repaired {len(repaired_skills)} skills)"
775
+ else:
776
+ message = "All PM skills verified and up-to-date"
607
777
  else:
608
- message = f"{len(warnings)} verification warnings found"
778
+ issue_count = len(missing_skills) + len(corrupted_skills)
779
+ message = f"{issue_count} PM skill issues found"
609
780
 
610
781
  return VerificationResult(
611
782
  verified=verified,
612
783
  warnings=warnings,
613
784
  missing_skills=missing_skills,
785
+ corrupted_skills=corrupted_skills,
614
786
  outdated_skills=outdated_skills,
615
787
  message=message,
616
- skill_count=len(deployed_skills),
788
+ skill_count=len(deployed_skills_data),
617
789
  )
618
790
 
619
791
  def get_deployed_skills(self, project_dir: Path) -> List[PMSkillInfo]:
@@ -636,10 +808,10 @@ class PMSkillsDeployerService(LoggerMixin):
636
808
  skills = []
637
809
  for skill_data in registry.get("skills", []):
638
810
  skill_name = skill_data["name"]
639
- deployed_path = deployment_dir / f"{skill_name}.md"
811
+ deployed_path = deployment_dir / skill_name / "SKILL.md"
640
812
 
641
813
  # Find source path (may not exist if bundled skills changed)
642
- source_path = self.bundled_pm_skills_path / f"{skill_name}.md"
814
+ source_path = self.bundled_pm_skills_path / skill_name / "SKILL.md"
643
815
 
644
816
  skills.append(
645
817
  PMSkillInfo(
@@ -682,7 +682,7 @@ class GitSkillSourceManager:
682
682
  try:
683
683
  with open(etag_cache_file, encoding="utf-8") as f:
684
684
  etag_cache = json.load(f)
685
- except Exception:
685
+ except Exception: # nosec B110 - intentional: proceed without cache on read failure
686
686
  pass
687
687
 
688
688
  cached_etag = etag_cache.get(str(local_path))
@@ -1163,6 +1163,10 @@ class GitSkillSourceManager:
1163
1163
  ) -> List[str]:
1164
1164
  """Remove skills from target directory that aren't in the filtered skill list.
1165
1165
 
1166
+ CRITICAL: Only removes MPM-managed skills (those in our cache). Custom user skills
1167
+ are preserved. This prevents accidental deletion of user-created skills that were
1168
+ never part of MPM's skill repository.
1169
+
1166
1170
  Uses fuzzy matching to handle both exact deployment names and short skill names.
1167
1171
  For example:
1168
1172
  - "toolchains-python-frameworks-flask" (deployed dir) matches "flask" (filter)
@@ -1213,6 +1217,40 @@ class GitSkillSourceManager:
1213
1217
 
1214
1218
  return False
1215
1219
 
1220
+ def is_mpm_managed_skill(skill_dir_name: str) -> bool:
1221
+ """Check if skill is managed by MPM (exists in our cache).
1222
+
1223
+ Custom user skills (not in cache) are NEVER deleted, even if not in filter.
1224
+ Only MPM-managed skills (in cache but not in filter) are candidates for removal.
1225
+
1226
+ Args:
1227
+ skill_dir_name: Name of deployed skill directory
1228
+
1229
+ Returns:
1230
+ True if skill exists in MPM cache (MPM-managed), False if custom user skill
1231
+ """
1232
+ # Check all configured skill sources for this skill
1233
+ for source in self.config.get_enabled_sources():
1234
+ cache_path = self._get_source_cache_path(source)
1235
+ if not cache_path.exists():
1236
+ continue
1237
+
1238
+ # Check if this skill directory exists anywhere in the cache
1239
+ # Use glob to find matching directories recursively
1240
+ matches = list(cache_path.rglob(f"*{skill_dir_name}*"))
1241
+ if matches:
1242
+ # Found in cache - this is MPM-managed
1243
+ self.logger.debug(
1244
+ f"Skill '{skill_dir_name}' found in cache at {matches[0]} - MPM-managed"
1245
+ )
1246
+ return True
1247
+
1248
+ # Not found in any cache - this is a custom user skill
1249
+ self.logger.debug(
1250
+ f"Skill '{skill_dir_name}' not found in cache - custom user skill, preserving"
1251
+ )
1252
+ return False
1253
+
1216
1254
  # Check each directory in target_dir
1217
1255
  if not target_dir.exists():
1218
1256
  return removed_skills
@@ -1229,6 +1267,15 @@ class GitSkillSourceManager:
1229
1267
 
1230
1268
  # Check if this skill directory should be kept (fuzzy matching)
1231
1269
  if not should_keep_skill(item.name):
1270
+ # CRITICAL: Check if this is an MPM-managed skill before deletion
1271
+ if not is_mpm_managed_skill(item.name):
1272
+ # This is a custom user skill - NEVER delete
1273
+ self.logger.debug(
1274
+ f"Preserving custom user skill (not in MPM cache): {item.name}"
1275
+ )
1276
+ continue
1277
+
1278
+ # It's MPM-managed but not in filter - safe to remove
1232
1279
  try:
1233
1280
  # Security: Validate path is within target_dir
1234
1281
  if not self._validate_safe_path(target_dir, item):
@@ -1244,7 +1291,9 @@ class GitSkillSourceManager:
1244
1291
  shutil.rmtree(item)
1245
1292
 
1246
1293
  removed_skills.append(item.name)
1247
- self.logger.info(f"Removed orphaned skill: {item.name}")
1294
+ self.logger.info(
1295
+ f"Removed orphaned MPM-managed skill: {item.name}"
1296
+ )
1248
1297
 
1249
1298
  except Exception as e:
1250
1299
  self.logger.warning(