claude-mpm 5.4.41__py3-none-any.whl → 5.6.23__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 (460) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/CLAUDE_MPM_OUTPUT_STYLE.md +66 -241
  3. claude_mpm/agents/CLAUDE_MPM_RESEARCH_OUTPUT_STYLE.md +413 -0
  4. claude_mpm/agents/CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md +109 -1925
  5. claude_mpm/agents/PM_INSTRUCTIONS.md +161 -298
  6. claude_mpm/agents/WORKFLOW.md +2 -0
  7. claude_mpm/agents/templates/circuit-breakers.md +26 -17
  8. claude_mpm/cli/__init__.py +5 -1
  9. claude_mpm/cli/commands/agents.py +2 -4
  10. claude_mpm/cli/commands/agents_reconcile.py +197 -0
  11. claude_mpm/cli/commands/autotodos.py +566 -0
  12. claude_mpm/cli/commands/commander.py +216 -0
  13. claude_mpm/cli/commands/configure.py +620 -21
  14. claude_mpm/cli/commands/configure_agent_display.py +3 -1
  15. claude_mpm/cli/commands/hook_errors.py +60 -60
  16. claude_mpm/cli/commands/monitor.py +2 -2
  17. claude_mpm/cli/commands/mpm_init/core.py +15 -8
  18. claude_mpm/cli/commands/profile.py +9 -10
  19. claude_mpm/cli/commands/run.py +35 -3
  20. claude_mpm/cli/commands/skill_source.py +51 -2
  21. claude_mpm/cli/commands/skills.py +182 -32
  22. claude_mpm/cli/executor.py +120 -16
  23. claude_mpm/cli/interactive/__init__.py +10 -0
  24. claude_mpm/cli/interactive/agent_wizard.py +30 -50
  25. claude_mpm/cli/interactive/questionary_styles.py +65 -0
  26. claude_mpm/cli/interactive/skill_selector.py +481 -0
  27. claude_mpm/cli/parsers/base_parser.py +76 -1
  28. claude_mpm/cli/parsers/commander_parser.py +116 -0
  29. claude_mpm/cli/parsers/profile_parser.py +0 -1
  30. claude_mpm/cli/parsers/run_parser.py +10 -0
  31. claude_mpm/cli/parsers/skill_source_parser.py +4 -0
  32. claude_mpm/cli/parsers/skills_parser.py +2 -3
  33. claude_mpm/cli/startup.py +527 -506
  34. claude_mpm/cli/startup_display.py +74 -6
  35. claude_mpm/cli/startup_logging.py +2 -2
  36. claude_mpm/cli/utils.py +7 -3
  37. claude_mpm/commander/__init__.py +78 -0
  38. claude_mpm/commander/adapters/__init__.py +60 -0
  39. claude_mpm/commander/adapters/auggie.py +260 -0
  40. claude_mpm/commander/adapters/base.py +288 -0
  41. claude_mpm/commander/adapters/claude_code.py +392 -0
  42. claude_mpm/commander/adapters/codex.py +237 -0
  43. claude_mpm/commander/adapters/communication.py +366 -0
  44. claude_mpm/commander/adapters/example_usage.py +310 -0
  45. claude_mpm/commander/adapters/mpm.py +389 -0
  46. claude_mpm/commander/adapters/registry.py +204 -0
  47. claude_mpm/commander/api/__init__.py +16 -0
  48. claude_mpm/commander/api/app.py +121 -0
  49. claude_mpm/commander/api/errors.py +133 -0
  50. claude_mpm/commander/api/routes/__init__.py +8 -0
  51. claude_mpm/commander/api/routes/events.py +184 -0
  52. claude_mpm/commander/api/routes/inbox.py +171 -0
  53. claude_mpm/commander/api/routes/messages.py +148 -0
  54. claude_mpm/commander/api/routes/projects.py +271 -0
  55. claude_mpm/commander/api/routes/sessions.py +226 -0
  56. claude_mpm/commander/api/routes/work.py +296 -0
  57. claude_mpm/commander/api/schemas.py +186 -0
  58. claude_mpm/commander/chat/__init__.py +7 -0
  59. claude_mpm/commander/chat/cli.py +146 -0
  60. claude_mpm/commander/chat/commands.py +96 -0
  61. claude_mpm/commander/chat/repl.py +310 -0
  62. claude_mpm/commander/config.py +51 -0
  63. claude_mpm/commander/config_loader.py +115 -0
  64. claude_mpm/commander/core/__init__.py +10 -0
  65. claude_mpm/commander/core/block_manager.py +325 -0
  66. claude_mpm/commander/core/response_manager.py +323 -0
  67. claude_mpm/commander/daemon.py +603 -0
  68. claude_mpm/commander/env_loader.py +59 -0
  69. claude_mpm/commander/events/__init__.py +26 -0
  70. claude_mpm/commander/events/manager.py +332 -0
  71. claude_mpm/commander/frameworks/__init__.py +12 -0
  72. claude_mpm/commander/frameworks/base.py +146 -0
  73. claude_mpm/commander/frameworks/claude_code.py +58 -0
  74. claude_mpm/commander/frameworks/mpm.py +62 -0
  75. claude_mpm/commander/inbox/__init__.py +16 -0
  76. claude_mpm/commander/inbox/dedup.py +128 -0
  77. claude_mpm/commander/inbox/inbox.py +224 -0
  78. claude_mpm/commander/inbox/models.py +70 -0
  79. claude_mpm/commander/instance_manager.py +450 -0
  80. claude_mpm/commander/llm/__init__.py +6 -0
  81. claude_mpm/commander/llm/openrouter_client.py +167 -0
  82. claude_mpm/commander/llm/summarizer.py +70 -0
  83. claude_mpm/commander/memory/__init__.py +45 -0
  84. claude_mpm/commander/memory/compression.py +347 -0
  85. claude_mpm/commander/memory/embeddings.py +230 -0
  86. claude_mpm/commander/memory/entities.py +310 -0
  87. claude_mpm/commander/memory/example_usage.py +290 -0
  88. claude_mpm/commander/memory/integration.py +325 -0
  89. claude_mpm/commander/memory/search.py +381 -0
  90. claude_mpm/commander/memory/store.py +657 -0
  91. claude_mpm/commander/models/__init__.py +18 -0
  92. claude_mpm/commander/models/events.py +121 -0
  93. claude_mpm/commander/models/project.py +162 -0
  94. claude_mpm/commander/models/work.py +214 -0
  95. claude_mpm/commander/parsing/__init__.py +20 -0
  96. claude_mpm/commander/parsing/extractor.py +132 -0
  97. claude_mpm/commander/parsing/output_parser.py +270 -0
  98. claude_mpm/commander/parsing/patterns.py +100 -0
  99. claude_mpm/commander/persistence/__init__.py +11 -0
  100. claude_mpm/commander/persistence/event_store.py +274 -0
  101. claude_mpm/commander/persistence/state_store.py +309 -0
  102. claude_mpm/commander/persistence/work_store.py +164 -0
  103. claude_mpm/commander/polling/__init__.py +13 -0
  104. claude_mpm/commander/polling/event_detector.py +104 -0
  105. claude_mpm/commander/polling/output_buffer.py +49 -0
  106. claude_mpm/commander/polling/output_poller.py +153 -0
  107. claude_mpm/commander/project_session.py +268 -0
  108. claude_mpm/commander/proxy/__init__.py +12 -0
  109. claude_mpm/commander/proxy/formatter.py +89 -0
  110. claude_mpm/commander/proxy/output_handler.py +191 -0
  111. claude_mpm/commander/proxy/relay.py +155 -0
  112. claude_mpm/commander/registry.py +410 -0
  113. claude_mpm/commander/runtime/__init__.py +10 -0
  114. claude_mpm/commander/runtime/executor.py +191 -0
  115. claude_mpm/commander/runtime/monitor.py +346 -0
  116. claude_mpm/commander/session/__init__.py +6 -0
  117. claude_mpm/commander/session/context.py +81 -0
  118. claude_mpm/commander/session/manager.py +59 -0
  119. claude_mpm/commander/tmux_orchestrator.py +361 -0
  120. claude_mpm/commander/web/__init__.py +1 -0
  121. claude_mpm/commander/work/__init__.py +30 -0
  122. claude_mpm/commander/work/executor.py +207 -0
  123. claude_mpm/commander/work/queue.py +405 -0
  124. claude_mpm/commander/workflow/__init__.py +27 -0
  125. claude_mpm/commander/workflow/event_handler.py +241 -0
  126. claude_mpm/commander/workflow/notifier.py +146 -0
  127. claude_mpm/commands/mpm-config.md +8 -0
  128. claude_mpm/commands/mpm-doctor.md +8 -0
  129. claude_mpm/commands/mpm-help.md +8 -0
  130. claude_mpm/commands/mpm-init.md +8 -0
  131. claude_mpm/commands/mpm-monitor.md +8 -0
  132. claude_mpm/commands/mpm-organize.md +8 -0
  133. claude_mpm/commands/mpm-postmortem.md +8 -0
  134. claude_mpm/commands/mpm-session-resume.md +9 -1
  135. claude_mpm/commands/mpm-status.md +8 -0
  136. claude_mpm/commands/mpm-ticket-view.md +8 -0
  137. claude_mpm/commands/mpm-version.md +8 -0
  138. claude_mpm/commands/mpm.md +8 -0
  139. claude_mpm/config/agent_presets.py +8 -7
  140. claude_mpm/config/skill_sources.py +16 -0
  141. claude_mpm/constants.py +1 -0
  142. claude_mpm/core/claude_runner.py +154 -2
  143. claude_mpm/core/config.py +35 -22
  144. claude_mpm/core/config_constants.py +74 -9
  145. claude_mpm/core/constants.py +56 -12
  146. claude_mpm/core/hook_manager.py +51 -3
  147. claude_mpm/core/interactive_session.py +12 -11
  148. claude_mpm/core/logger.py +26 -9
  149. claude_mpm/core/logging_utils.py +35 -11
  150. claude_mpm/core/network_config.py +148 -0
  151. claude_mpm/core/oneshot_session.py +7 -6
  152. claude_mpm/core/optimized_startup.py +3 -1
  153. claude_mpm/core/output_style_manager.py +63 -18
  154. claude_mpm/core/shared/config_loader.py +3 -1
  155. claude_mpm/core/socketio_pool.py +13 -5
  156. claude_mpm/core/unified_config.py +54 -8
  157. claude_mpm/core/unified_paths.py +95 -90
  158. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.C33zOoyM.css +1 -0
  159. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.CW1J-YuA.css +1 -0
  160. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/1WZnGYqX.js +24 -0
  161. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/67pF3qNn.js +1 -0
  162. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/6RxdMKe4.js +1 -0
  163. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/8cZrfX0h.js +60 -0
  164. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/9a6T2nm-.js +7 -0
  165. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/B443AUzu.js +1 -0
  166. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/B8AwtY2H.js +1 -0
  167. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BF15LAsF.js +1 -0
  168. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BQaXIfA_.js +331 -0
  169. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BRcwIQNr.js +4 -0
  170. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{uj46x2Wr.js → BSNlmTZj.js} +1 -1
  171. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BV6nKitt.js +43 -0
  172. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BViJ8lZt.js +128 -0
  173. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BcQ-Q0FE.js +1 -0
  174. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Bpyvgze_.js +30 -0
  175. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BzTRqg-z.js +1 -0
  176. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C0Fr8dve.js +1 -0
  177. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C3rbW_a-.js +1 -0
  178. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C8WYN38h.js +1 -0
  179. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C9I8FlXH.js +61 -0
  180. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CIQcWgO2.js +36 -0
  181. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CIctN7YN.js +7 -0
  182. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CKrS_JZW.js +145 -0
  183. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CR6P9C4A.js +89 -0
  184. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CRRR9MD_.js +2 -0
  185. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CRcR2DqT.js +334 -0
  186. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CSXtMOf0.js +1 -0
  187. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CT-sbxSk.js +1 -0
  188. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CWm6DJsp.js +1 -0
  189. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CmKTTxBW.js +1 -0
  190. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CpqQ1Kzn.js +1 -0
  191. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Cu_Erd72.js +261 -0
  192. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/D2nGpDRe.js +1 -0
  193. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/D9iCMida.js +267 -0
  194. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/D9ykgMoY.js +10 -0
  195. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DL2Ldur1.js +1 -0
  196. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DPfltzjH.js +165 -0
  197. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{N4qtv3Hx.js → DR8nis88.js} +2 -2
  198. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DUliQN2b.js +1 -0
  199. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DVp1hx9R.js +1 -0
  200. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DXlhR01x.js +122 -0
  201. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/D_lyTybS.js +1 -0
  202. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DngoTTgh.js +1 -0
  203. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DqkmHtDC.js +220 -0
  204. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DsDh8EYs.js +1 -0
  205. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DypDmXgd.js +139 -0
  206. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Gi6I4Gst.js +1 -0
  207. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/IPYC-LnN.js +162 -0
  208. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/JTLiF7dt.js +24 -0
  209. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/JpevfAFt.js +68 -0
  210. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DjhvlsAc.js → NqQ1dWOy.js} +1 -1
  211. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/R8CEIRAd.js +2 -0
  212. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Zxy7qc-l.js +64 -0
  213. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/q9Hm6zAU.js +1 -0
  214. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/qtd3IeO4.js +15 -0
  215. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/ulBFON_C.js +65 -0
  216. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/wQVh1CoA.js +10 -0
  217. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/app.Dr7t0z2J.js +2 -0
  218. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.BGhZHUS3.js +1 -0
  219. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/{0.CAGBuiOw.js → 0.RgBboRvH.js} +1 -1
  220. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/1.DG-KkbDf.js +1 -0
  221. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.D_jnf-x6.js +1 -0
  222. claude_mpm/dashboard/static/svelte-build/_app/version.json +1 -1
  223. claude_mpm/dashboard/static/svelte-build/index.html +11 -11
  224. claude_mpm/dashboard-svelte/node_modules/katex/src/fonts/generate_fonts.py +58 -0
  225. claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/extract_tfms.py +114 -0
  226. claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/extract_ttfs.py +122 -0
  227. claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/format_json.py +28 -0
  228. claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/parse_tfm.py +211 -0
  229. claude_mpm/experimental/cli_enhancements.py +2 -1
  230. claude_mpm/hooks/claude_hooks/INTEGRATION_EXAMPLE.md +243 -0
  231. claude_mpm/hooks/claude_hooks/README_AUTO_PAUSE.md +403 -0
  232. claude_mpm/hooks/claude_hooks/__pycache__/auto_pause_handler.cpython-311.pyc +0 -0
  233. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-311.pyc +0 -0
  234. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-311.pyc +0 -0
  235. claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-311.pyc +0 -0
  236. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-311.pyc +0 -0
  237. claude_mpm/hooks/claude_hooks/auto_pause_handler.py +485 -0
  238. claude_mpm/hooks/claude_hooks/event_handlers.py +305 -87
  239. claude_mpm/hooks/claude_hooks/hook_handler.py +106 -89
  240. claude_mpm/hooks/claude_hooks/hook_wrapper.sh +6 -11
  241. claude_mpm/hooks/claude_hooks/installer.py +116 -8
  242. claude_mpm/hooks/claude_hooks/memory_integration.py +51 -31
  243. claude_mpm/hooks/claude_hooks/response_tracking.py +42 -59
  244. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-311.pyc +0 -0
  245. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-311.pyc +0 -0
  246. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-311.pyc +0 -0
  247. claude_mpm/hooks/claude_hooks/services/connection_manager.py +39 -24
  248. claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +36 -103
  249. claude_mpm/hooks/claude_hooks/services/state_manager.py +23 -36
  250. claude_mpm/hooks/claude_hooks/services/subagent_processor.py +73 -75
  251. claude_mpm/hooks/kuzu_memory_hook.py +5 -5
  252. claude_mpm/hooks/session_resume_hook.py +89 -1
  253. claude_mpm/hooks/templates/pre_tool_use_template.py +10 -2
  254. claude_mpm/init.py +215 -2
  255. claude_mpm/scripts/claude-hook-handler.sh +43 -16
  256. claude_mpm/services/agents/agent_recommendation_service.py +8 -8
  257. claude_mpm/services/agents/agent_selection_service.py +2 -2
  258. claude_mpm/services/agents/cache_git_manager.py +1 -1
  259. claude_mpm/services/agents/deployment/agent_discovery_service.py +3 -1
  260. claude_mpm/services/agents/deployment/agent_format_converter.py +25 -13
  261. claude_mpm/services/agents/deployment/agent_template_builder.py +37 -17
  262. claude_mpm/services/agents/deployment/async_agent_deployment.py +31 -27
  263. claude_mpm/services/agents/deployment/deployment_reconciler.py +577 -0
  264. claude_mpm/services/agents/deployment/local_template_deployment.py +3 -1
  265. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +36 -8
  266. claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +50 -26
  267. claude_mpm/services/agents/deployment/startup_reconciliation.py +138 -0
  268. claude_mpm/services/agents/git_source_manager.py +21 -2
  269. claude_mpm/services/agents/loading/framework_agent_loader.py +75 -2
  270. claude_mpm/services/agents/single_tier_deployment_service.py +4 -4
  271. claude_mpm/services/agents/sources/git_source_sync_service.py +116 -5
  272. claude_mpm/services/agents/startup_sync.py +5 -2
  273. claude_mpm/services/cli/__init__.py +3 -0
  274. claude_mpm/services/cli/incremental_pause_manager.py +561 -0
  275. claude_mpm/services/cli/session_resume_helper.py +10 -2
  276. claude_mpm/services/delegation_detector.py +175 -0
  277. claude_mpm/services/diagnostics/checks/agent_sources_check.py +30 -0
  278. claude_mpm/services/diagnostics/checks/configuration_check.py +24 -0
  279. claude_mpm/services/diagnostics/checks/installation_check.py +22 -0
  280. claude_mpm/services/diagnostics/checks/mcp_services_check.py +23 -0
  281. claude_mpm/services/diagnostics/doctor_reporter.py +31 -1
  282. claude_mpm/services/diagnostics/models.py +14 -1
  283. claude_mpm/services/event_log.py +325 -0
  284. claude_mpm/services/infrastructure/__init__.py +4 -0
  285. claude_mpm/services/infrastructure/context_usage_tracker.py +291 -0
  286. claude_mpm/services/infrastructure/resume_log_generator.py +24 -5
  287. claude_mpm/services/monitor/daemon_manager.py +15 -4
  288. claude_mpm/services/monitor/management/lifecycle.py +8 -3
  289. claude_mpm/services/monitor/server.py +106 -16
  290. claude_mpm/services/pm_skills_deployer.py +302 -94
  291. claude_mpm/services/profile_manager.py +10 -4
  292. claude_mpm/services/skills/git_skill_source_manager.py +192 -29
  293. claude_mpm/services/skills/selective_skill_deployer.py +211 -46
  294. claude_mpm/services/skills/skill_discovery_service.py +74 -4
  295. claude_mpm/services/skills_deployer.py +192 -70
  296. claude_mpm/services/socketio/handlers/hook.py +14 -7
  297. claude_mpm/services/socketio/server/main.py +12 -4
  298. claude_mpm/skills/__init__.py +2 -1
  299. claude_mpm/skills/bundled/collaboration/brainstorming/SKILL.md +79 -0
  300. claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/SKILL.md +178 -0
  301. claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/agent-prompts.md +577 -0
  302. claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/coordination-patterns.md +467 -0
  303. claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/examples.md +537 -0
  304. claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/troubleshooting.md +730 -0
  305. claude_mpm/skills/bundled/collaboration/git-worktrees.md +317 -0
  306. claude_mpm/skills/bundled/collaboration/requesting-code-review/SKILL.md +112 -0
  307. claude_mpm/skills/bundled/collaboration/requesting-code-review/references/code-reviewer-template.md +146 -0
  308. claude_mpm/skills/bundled/collaboration/requesting-code-review/references/review-examples.md +412 -0
  309. claude_mpm/skills/bundled/collaboration/stacked-prs.md +251 -0
  310. claude_mpm/skills/bundled/collaboration/writing-plans/SKILL.md +81 -0
  311. claude_mpm/skills/bundled/collaboration/writing-plans/references/best-practices.md +362 -0
  312. claude_mpm/skills/bundled/collaboration/writing-plans/references/plan-structure-templates.md +312 -0
  313. claude_mpm/skills/bundled/debugging/root-cause-tracing/SKILL.md +152 -0
  314. claude_mpm/skills/bundled/debugging/root-cause-tracing/references/advanced-techniques.md +668 -0
  315. claude_mpm/skills/bundled/debugging/root-cause-tracing/references/examples.md +587 -0
  316. claude_mpm/skills/bundled/debugging/root-cause-tracing/references/integration.md +438 -0
  317. claude_mpm/skills/bundled/debugging/root-cause-tracing/references/tracing-techniques.md +391 -0
  318. claude_mpm/skills/bundled/debugging/systematic-debugging/CREATION-LOG.md +119 -0
  319. claude_mpm/skills/bundled/debugging/systematic-debugging/SKILL.md +148 -0
  320. claude_mpm/skills/bundled/debugging/systematic-debugging/references/anti-patterns.md +483 -0
  321. claude_mpm/skills/bundled/debugging/systematic-debugging/references/examples.md +452 -0
  322. claude_mpm/skills/bundled/debugging/systematic-debugging/references/troubleshooting.md +449 -0
  323. claude_mpm/skills/bundled/debugging/systematic-debugging/references/workflow.md +411 -0
  324. claude_mpm/skills/bundled/debugging/systematic-debugging/test-academic.md +14 -0
  325. claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-1.md +58 -0
  326. claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-2.md +68 -0
  327. claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-3.md +69 -0
  328. claude_mpm/skills/bundled/debugging/verification-before-completion/SKILL.md +131 -0
  329. claude_mpm/skills/bundled/debugging/verification-before-completion/references/gate-function.md +325 -0
  330. claude_mpm/skills/bundled/debugging/verification-before-completion/references/integration-and-workflows.md +490 -0
  331. claude_mpm/skills/bundled/debugging/verification-before-completion/references/red-flags-and-failures.md +425 -0
  332. claude_mpm/skills/bundled/debugging/verification-before-completion/references/verification-patterns.md +499 -0
  333. claude_mpm/skills/bundled/infrastructure/env-manager/INTEGRATION.md +611 -0
  334. claude_mpm/skills/bundled/infrastructure/env-manager/README.md +596 -0
  335. claude_mpm/skills/bundled/infrastructure/env-manager/SKILL.md +260 -0
  336. claude_mpm/skills/bundled/infrastructure/env-manager/examples/nextjs-env-structure.md +315 -0
  337. claude_mpm/skills/bundled/infrastructure/env-manager/references/frameworks.md +436 -0
  338. claude_mpm/skills/bundled/infrastructure/env-manager/references/security.md +433 -0
  339. claude_mpm/skills/bundled/infrastructure/env-manager/references/synchronization.md +452 -0
  340. claude_mpm/skills/bundled/infrastructure/env-manager/references/troubleshooting.md +404 -0
  341. claude_mpm/skills/bundled/infrastructure/env-manager/references/validation.md +420 -0
  342. claude_mpm/skills/bundled/main/artifacts-builder/SKILL.md +86 -0
  343. claude_mpm/skills/bundled/main/internal-comms/SKILL.md +43 -0
  344. claude_mpm/skills/bundled/main/internal-comms/examples/3p-updates.md +47 -0
  345. claude_mpm/skills/bundled/main/internal-comms/examples/company-newsletter.md +65 -0
  346. claude_mpm/skills/bundled/main/internal-comms/examples/faq-answers.md +30 -0
  347. claude_mpm/skills/bundled/main/internal-comms/examples/general-comms.md +16 -0
  348. claude_mpm/skills/bundled/main/mcp-builder/SKILL.md +160 -0
  349. claude_mpm/skills/bundled/main/mcp-builder/reference/design_principles.md +412 -0
  350. claude_mpm/skills/bundled/main/mcp-builder/reference/evaluation.md +602 -0
  351. claude_mpm/skills/bundled/main/mcp-builder/reference/mcp_best_practices.md +915 -0
  352. claude_mpm/skills/bundled/main/mcp-builder/reference/node_mcp_server.md +916 -0
  353. claude_mpm/skills/bundled/main/mcp-builder/reference/python_mcp_server.md +752 -0
  354. claude_mpm/skills/bundled/main/mcp-builder/reference/workflow.md +1237 -0
  355. claude_mpm/skills/bundled/main/skill-creator/SKILL.md +189 -0
  356. claude_mpm/skills/bundled/main/skill-creator/references/best-practices.md +500 -0
  357. claude_mpm/skills/bundled/main/skill-creator/references/creation-workflow.md +464 -0
  358. claude_mpm/skills/bundled/main/skill-creator/references/examples.md +619 -0
  359. claude_mpm/skills/bundled/main/skill-creator/references/progressive-disclosure.md +437 -0
  360. claude_mpm/skills/bundled/main/skill-creator/references/skill-structure.md +231 -0
  361. claude_mpm/skills/bundled/php/espocrm-development/SKILL.md +170 -0
  362. claude_mpm/skills/bundled/php/espocrm-development/references/architecture.md +602 -0
  363. claude_mpm/skills/bundled/php/espocrm-development/references/common-tasks.md +821 -0
  364. claude_mpm/skills/bundled/php/espocrm-development/references/development-workflow.md +742 -0
  365. claude_mpm/skills/bundled/php/espocrm-development/references/frontend-customization.md +726 -0
  366. claude_mpm/skills/bundled/php/espocrm-development/references/hooks-and-services.md +764 -0
  367. claude_mpm/skills/bundled/php/espocrm-development/references/testing-debugging.md +831 -0
  368. claude_mpm/skills/bundled/pm/mpm/SKILL.md +38 -0
  369. claude_mpm/skills/bundled/pm/mpm-agent-update-workflow/SKILL.md +75 -0
  370. claude_mpm/skills/bundled/pm/mpm-bug-reporting/SKILL.md +248 -0
  371. claude_mpm/skills/bundled/pm/mpm-circuit-breaker-enforcement/SKILL.md +476 -0
  372. claude_mpm/skills/bundled/pm/mpm-config/SKILL.md +29 -0
  373. claude_mpm/skills/bundled/pm/mpm-delegation-patterns/SKILL.md +167 -0
  374. claude_mpm/skills/bundled/pm/mpm-doctor/SKILL.md +53 -0
  375. claude_mpm/skills/bundled/pm/mpm-git-file-tracking/SKILL.md +113 -0
  376. claude_mpm/skills/bundled/pm/mpm-help/SKILL.md +35 -0
  377. claude_mpm/skills/bundled/pm/mpm-init/SKILL.md +125 -0
  378. claude_mpm/skills/bundled/pm/mpm-monitor/SKILL.md +32 -0
  379. claude_mpm/skills/bundled/pm/mpm-organize/SKILL.md +121 -0
  380. claude_mpm/skills/bundled/pm/mpm-postmortem/SKILL.md +22 -0
  381. claude_mpm/skills/bundled/pm/mpm-pr-workflow/SKILL.md +124 -0
  382. claude_mpm/skills/bundled/pm/mpm-session-management/SKILL.md +312 -0
  383. claude_mpm/skills/bundled/pm/mpm-session-pause/SKILL.md +170 -0
  384. claude_mpm/skills/bundled/pm/mpm-session-resume/SKILL.md +31 -0
  385. claude_mpm/skills/bundled/pm/mpm-status/SKILL.md +37 -0
  386. claude_mpm/skills/bundled/pm/mpm-teaching-mode/SKILL.md +657 -0
  387. claude_mpm/skills/bundled/pm/mpm-ticket-view/SKILL.md +110 -0
  388. claude_mpm/skills/bundled/pm/mpm-ticketing-integration/SKILL.md +154 -0
  389. claude_mpm/skills/bundled/pm/mpm-tool-usage-guide/SKILL.md +386 -0
  390. claude_mpm/skills/bundled/pm/mpm-verification-protocols/SKILL.md +198 -0
  391. claude_mpm/skills/bundled/pm/mpm-version/SKILL.md +21 -0
  392. claude_mpm/skills/bundled/react/flexlayout-react.md +742 -0
  393. claude_mpm/skills/bundled/rust/desktop-applications/SKILL.md +226 -0
  394. claude_mpm/skills/bundled/rust/desktop-applications/references/architecture-patterns.md +901 -0
  395. claude_mpm/skills/bundled/rust/desktop-applications/references/native-gui-frameworks.md +901 -0
  396. claude_mpm/skills/bundled/rust/desktop-applications/references/platform-integration.md +775 -0
  397. claude_mpm/skills/bundled/rust/desktop-applications/references/state-management.md +937 -0
  398. claude_mpm/skills/bundled/rust/desktop-applications/references/tauri-framework.md +770 -0
  399. claude_mpm/skills/bundled/rust/desktop-applications/references/testing-deployment.md +961 -0
  400. claude_mpm/skills/bundled/security-scanning.md +112 -0
  401. claude_mpm/skills/bundled/tauri/tauri-async-patterns.md +495 -0
  402. claude_mpm/skills/bundled/tauri/tauri-build-deploy.md +599 -0
  403. claude_mpm/skills/bundled/tauri/tauri-command-patterns.md +535 -0
  404. claude_mpm/skills/bundled/tauri/tauri-error-handling.md +613 -0
  405. claude_mpm/skills/bundled/tauri/tauri-event-system.md +648 -0
  406. claude_mpm/skills/bundled/tauri/tauri-file-system.md +673 -0
  407. claude_mpm/skills/bundled/tauri/tauri-frontend-integration.md +767 -0
  408. claude_mpm/skills/bundled/tauri/tauri-performance.md +669 -0
  409. claude_mpm/skills/bundled/tauri/tauri-state-management.md +573 -0
  410. claude_mpm/skills/bundled/tauri/tauri-testing.md +384 -0
  411. claude_mpm/skills/bundled/tauri/tauri-window-management.md +628 -0
  412. claude_mpm/skills/bundled/testing/condition-based-waiting/SKILL.md +119 -0
  413. claude_mpm/skills/bundled/testing/condition-based-waiting/references/patterns-and-implementation.md +253 -0
  414. claude_mpm/skills/bundled/testing/test-driven-development/SKILL.md +145 -0
  415. claude_mpm/skills/bundled/testing/test-driven-development/references/anti-patterns.md +543 -0
  416. claude_mpm/skills/bundled/testing/test-driven-development/references/examples.md +741 -0
  417. claude_mpm/skills/bundled/testing/test-driven-development/references/integration.md +470 -0
  418. claude_mpm/skills/bundled/testing/test-driven-development/references/philosophy.md +458 -0
  419. claude_mpm/skills/bundled/testing/test-driven-development/references/workflow.md +639 -0
  420. claude_mpm/skills/bundled/testing/test-quality-inspector/SKILL.md +458 -0
  421. claude_mpm/skills/bundled/testing/test-quality-inspector/examples/example-inspection-report.md +411 -0
  422. claude_mpm/skills/bundled/testing/test-quality-inspector/references/assertion-quality.md +317 -0
  423. claude_mpm/skills/bundled/testing/test-quality-inspector/references/inspection-checklist.md +270 -0
  424. claude_mpm/skills/bundled/testing/test-quality-inspector/references/red-flags.md +436 -0
  425. claude_mpm/skills/bundled/testing/testing-anti-patterns/SKILL.md +140 -0
  426. claude_mpm/skills/bundled/testing/testing-anti-patterns/references/completeness-anti-patterns.md +572 -0
  427. claude_mpm/skills/bundled/testing/testing-anti-patterns/references/core-anti-patterns.md +411 -0
  428. claude_mpm/skills/bundled/testing/testing-anti-patterns/references/detection-guide.md +569 -0
  429. claude_mpm/skills/bundled/testing/testing-anti-patterns/references/tdd-connection.md +695 -0
  430. claude_mpm/skills/bundled/testing/webapp-testing/SKILL.md +184 -0
  431. claude_mpm/skills/bundled/testing/webapp-testing/decision-tree.md +459 -0
  432. claude_mpm/skills/bundled/testing/webapp-testing/playwright-patterns.md +479 -0
  433. claude_mpm/skills/bundled/testing/webapp-testing/reconnaissance-pattern.md +687 -0
  434. claude_mpm/skills/bundled/testing/webapp-testing/server-management.md +758 -0
  435. claude_mpm/skills/bundled/testing/webapp-testing/troubleshooting.md +868 -0
  436. claude_mpm/skills/registry.py +295 -90
  437. claude_mpm/skills/skill_manager.py +29 -23
  438. claude_mpm/templates/.pre-commit-config.yaml +112 -0
  439. claude_mpm/utils/agent_dependency_loader.py +103 -4
  440. claude_mpm/utils/robust_installer.py +45 -24
  441. claude_mpm-5.6.23.dist-info/METADATA +393 -0
  442. {claude_mpm-5.4.41.dist-info → claude_mpm-5.6.23.dist-info}/RECORD +447 -149
  443. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.B_FtCwCQ.css +0 -1
  444. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.Cl_eSA4x.css +0 -1
  445. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BgChzWQ1.js +0 -1
  446. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CIXEwuWe.js +0 -1
  447. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CWc5urbQ.js +0 -1
  448. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DMkZpdF2.js +0 -2
  449. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/app.DTL5mJO-.js +0 -2
  450. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.DzuEhzqh.js +0 -1
  451. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/1.DFLC8jdE.js +0 -1
  452. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.DPvEihJJ.js +0 -10
  453. claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-311.pyc +0 -0
  454. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager.cpython-311.pyc +0 -0
  455. claude_mpm-5.4.41.dist-info/METADATA +0 -998
  456. {claude_mpm-5.4.41.dist-info → claude_mpm-5.6.23.dist-info}/WHEEL +0 -0
  457. {claude_mpm-5.4.41.dist-info → claude_mpm-5.6.23.dist-info}/entry_points.txt +0 -0
  458. {claude_mpm-5.4.41.dist-info → claude_mpm-5.6.23.dist-info}/licenses/LICENSE +0 -0
  459. {claude_mpm-5.4.41.dist-info → claude_mpm-5.6.23.dist-info}/licenses/LICENSE-FAQ.md +0 -0
  460. {claude_mpm-5.4.41.dist-info → claude_mpm-5.6.23.dist-info}/top_level.txt +0 -0
claude_mpm/cli/startup.py CHANGED
@@ -161,7 +161,25 @@ def setup_early_environment(argv):
161
161
  # CRITICAL: Suppress ALL logging by default
162
162
  # This catches all loggers (claude_mpm.*, service.*, framework_loader, etc.)
163
163
  # This will be overridden by setup_mcp_server_logging() based on user preference
164
- logging.getLogger().setLevel(logging.CRITICAL + 1) # Root logger catches everything
164
+ root_logger = logging.getLogger()
165
+ root_logger.setLevel(logging.CRITICAL + 1) # Root logger catches everything
166
+ root_logger.handlers = [] # Remove any handlers
167
+
168
+ # Also suppress common module loggers explicitly to prevent handler leakage
169
+ for logger_name in [
170
+ "claude_mpm",
171
+ "path_resolver",
172
+ "file_loader",
173
+ "framework_loader",
174
+ "service",
175
+ "instruction_loader",
176
+ "agent_loader",
177
+ "startup",
178
+ ]:
179
+ module_logger = logging.getLogger(logger_name)
180
+ module_logger.setLevel(logging.CRITICAL + 1)
181
+ module_logger.handlers = []
182
+ module_logger.propagate = False
165
183
 
166
184
  # Process argv
167
185
  if argv is None:
@@ -191,7 +209,17 @@ def should_skip_background_services(args, processed_argv):
191
209
  skip_commands = ["--version", "-v", "--help", "-h"]
192
210
  return any(cmd in (processed_argv or sys.argv[1:]) for cmd in skip_commands) or (
193
211
  hasattr(args, "command")
194
- and args.command in ["info", "doctor", "config", "mcp", "configure"]
212
+ and args.command
213
+ in [
214
+ "info",
215
+ "doctor",
216
+ "config",
217
+ "mcp",
218
+ "configure",
219
+ "hook-errors",
220
+ "autotodos",
221
+ "commander",
222
+ ]
195
223
  )
196
224
 
197
225
 
@@ -234,7 +262,7 @@ def deploy_bundled_skills():
234
262
  if not skills_config.get("auto_deploy", True):
235
263
  # Auto-deploy disabled, skip silently
236
264
  return
237
- except Exception:
265
+ except Exception: # nosec B110
238
266
  # If config loading fails, assume auto-deploy is enabled (default)
239
267
  pass
240
268
 
@@ -308,63 +336,60 @@ def deploy_output_style_on_startup():
308
336
  communication without emojis and exclamation points. Styles are project-specific
309
337
  to allow different projects to have different communication styles.
310
338
 
311
- DESIGN DECISION: This is non-blocking and idempotent. Deploys to project-level
312
- directory (.claude/settings/output-styles/) instead of user-level to maintain
313
- project isolation.
339
+ DESIGN DECISION: This is non-blocking and idempotent. Deploys to user-level
340
+ directory (~/.claude/output-styles/) which is the official Claude Code location
341
+ for custom output styles.
314
342
 
315
- Deploys two styles:
316
- - claude-mpm-style.md (professional mode)
343
+ Deploys all styles:
344
+ - claude-mpm.md (professional mode)
317
345
  - claude-mpm-teacher.md (teaching mode)
346
+ - claude-mpm-research.md (research mode - for codebase analysis)
318
347
  """
319
348
  try:
320
- import shutil
321
- from pathlib import Path
349
+ from ..core.output_style_manager import OutputStyleManager
322
350
 
323
- # Source files (in framework package)
324
- package_dir = Path(__file__).parent.parent / "agents"
325
- professional_source = package_dir / "CLAUDE_MPM_OUTPUT_STYLE.md"
326
- teacher_source = package_dir / "CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md"
351
+ # Initialize the output style manager
352
+ manager = OutputStyleManager()
327
353
 
328
- # Target directory (PROJECT-LEVEL, not user-level)
329
- project_dir = Path.cwd()
330
- output_styles_dir = project_dir / ".claude" / "settings" / "output-styles"
331
- professional_target = output_styles_dir / "claude-mpm-style.md"
332
- teacher_target = output_styles_dir / "claude-mpm-teacher.md"
333
-
334
- # Create directory if it doesn't exist
335
- output_styles_dir.mkdir(parents=True, exist_ok=True)
336
-
337
- # Check if already deployed (both files exist and have content)
338
- already_deployed = (
339
- professional_target.exists()
340
- and teacher_target.exists()
341
- and professional_target.stat().st_size > 0
342
- and teacher_target.stat().st_size > 0
343
- )
354
+ # Check if Claude Code version supports output styles (>= 1.0.83)
355
+ if not manager.supports_output_styles():
356
+ # Skip deployment for older versions
357
+ # The manager will fall back to injecting content directly
358
+ return
344
359
 
345
- if already_deployed:
360
+ # Check if all styles are already deployed and up-to-date
361
+ all_up_to_date = True
362
+ for style_config in manager.styles.values():
363
+ source_path = style_config["source"]
364
+ target_path = style_config["target"]
365
+
366
+ if not (
367
+ target_path.exists()
368
+ and source_path.exists()
369
+ and target_path.stat().st_size == source_path.stat().st_size
370
+ ):
371
+ all_up_to_date = False
372
+ break
373
+
374
+ if all_up_to_date:
346
375
  # Show feedback that output styles are ready
347
376
  print("✓ Output styles ready", flush=True)
348
377
  return
349
378
 
350
- # Deploy both styles
351
- deployed_count = 0
352
- if professional_source.exists():
353
- shutil.copy2(professional_source, professional_target)
354
- deployed_count += 1
379
+ # Deploy all styles using the manager
380
+ results = manager.deploy_all_styles(activate_default=True)
355
381
 
356
- if teacher_source.exists():
357
- shutil.copy2(teacher_source, teacher_target)
358
- deployed_count += 1
382
+ # Count successful deployments
383
+ deployed_count = sum(1 for success in results.values() if success)
359
384
 
360
385
  if deployed_count > 0:
361
386
  print(f"✓ Output styles deployed ({deployed_count} styles)", flush=True)
362
387
  else:
363
- # Source files missing - log but don't fail
388
+ # Deployment failed - log but don't fail startup
364
389
  from ..core.logger import get_logger
365
390
 
366
391
  logger = get_logger("cli")
367
- logger.debug("Output style source files not found")
392
+ logger.debug("Failed to deploy any output styles")
368
393
 
369
394
  except Exception as e:
370
395
  # Non-critical - log but don't fail startup
@@ -453,7 +478,95 @@ def _cleanup_orphaned_agents(deploy_target: Path, deployed_agents: list[str]) ->
453
478
  return removed_count
454
479
 
455
480
 
456
- def sync_remote_agents_on_startup():
481
+ def _save_deployment_state_after_reconciliation(
482
+ agent_result, project_path: Path
483
+ ) -> None:
484
+ """Save deployment state after reconciliation to prevent duplicate deployment.
485
+
486
+ WHY: After perform_startup_reconciliation() deploys agents to .claude/agents/,
487
+ we need to save a deployment state file so that ClaudeRunner.setup_agents()
488
+ can detect agents are already deployed and skip redundant deployment.
489
+
490
+ This prevents the "✓ Deployed 31 native agents" duplicate deployment that
491
+ occurs when setup_agents() doesn't know reconciliation already ran.
492
+
493
+ Args:
494
+ agent_result: DeploymentResult from perform_startup_reconciliation()
495
+ project_path: Project root directory
496
+
497
+ DESIGN DECISION: Use same state file format as ClaudeRunner._save_deployment_state()
498
+ Located at: .claude-mpm/cache/deployment_state.json
499
+
500
+ State file format:
501
+ {
502
+ "version": "5.6.13",
503
+ "agent_count": 15,
504
+ "deployment_hash": "sha256:...",
505
+ "deployed_at": 1234567890.123
506
+ }
507
+ """
508
+ import hashlib
509
+ import json
510
+ import time
511
+
512
+ from ..core.logger import get_logger
513
+
514
+ logger = get_logger("cli")
515
+
516
+ try:
517
+ # Get version from package
518
+ from claude_mpm import __version__
519
+
520
+ # Path to state file (matches ClaudeRunner._get_deployment_state_path())
521
+ state_file = project_path / ".claude-mpm" / "cache" / "deployment_state.json"
522
+ agents_dir = project_path / ".claude" / "agents"
523
+
524
+ # Count deployed agents
525
+ if agents_dir.exists():
526
+ agent_count = len(list(agents_dir.glob("*.md")))
527
+ else:
528
+ agent_count = 0
529
+
530
+ # Calculate deployment hash (matches ClaudeRunner._calculate_deployment_hash())
531
+ # CRITICAL: Must match exact hash algorithm used in ClaudeRunner
532
+ # Hashes filename + file content (not mtime) for consistency
533
+ deployment_hash = ""
534
+ if agents_dir.exists():
535
+ agent_files = sorted(agents_dir.glob("*.md"))
536
+ hash_obj = hashlib.sha256()
537
+ for agent_file in agent_files:
538
+ # Include filename and content in hash (matches ClaudeRunner)
539
+ hash_obj.update(agent_file.name.encode())
540
+ try:
541
+ hash_obj.update(agent_file.read_bytes())
542
+ except Exception as e:
543
+ logger.debug(f"Error reading {agent_file} for hash: {e}")
544
+
545
+ deployment_hash = hash_obj.hexdigest()
546
+
547
+ # Create state data
548
+ state_data = {
549
+ "version": __version__,
550
+ "agent_count": agent_count,
551
+ "deployment_hash": deployment_hash,
552
+ "deployed_at": time.time(),
553
+ }
554
+
555
+ # Ensure directory exists
556
+ state_file.parent.mkdir(parents=True, exist_ok=True)
557
+
558
+ # Write state file
559
+ state_file.write_text(json.dumps(state_data, indent=2))
560
+ logger.debug(
561
+ f"Saved deployment state after reconciliation: {agent_count} agents"
562
+ )
563
+
564
+ except Exception as e:
565
+ # Non-critical error - log but don't fail startup
566
+ logger.debug(f"Failed to save deployment state: {e}")
567
+
568
+
569
+ def sync_remote_agents_on_startup(force_sync: bool = False):
457
570
  """
458
571
  Synchronize agent templates from remote sources on startup.
459
572
 
@@ -466,28 +579,28 @@ def sync_remote_agents_on_startup():
466
579
  block startup to ensure claude-mpm remains functional.
467
580
 
468
581
  Workflow:
469
- 1. Cleanup legacy agent cache directories (if any)
470
- 2. Sync all enabled Git sources (download/cache files) - Phase 1 progress bar
471
- 3. Deploy agents to ~/.claude/agents/ - Phase 2 progress bar
472
- 4. Cleanup orphaned agents (ours but no longer deployed) - Phase 3
582
+ 1. Sync all enabled Git sources (download/cache files) - Phase 1 progress bar
583
+ 2. Deploy agents to ~/.claude/agents/ - Phase 2 progress bar
584
+ 3. Cleanup orphaned agents (ours but no longer deployed) - Phase 3
585
+ 4. Cleanup legacy agent cache directories (after sync/deployment) - Phase 4
473
586
  5. Log deployment results
474
- """
475
- # Cleanup legacy cache directories first (before syncing)
476
- cleanup_legacy_agent_cache()
477
587
 
478
- # DEPRECATED: Legacy warning - replaced by automatic cleanup above
588
+ Args:
589
+ force_sync: Force download even if cache is fresh (bypasses ETag).
590
+ """
591
+ # DEPRECATED: Legacy warning - no-op function, kept for compatibility
479
592
  check_legacy_cache()
480
593
 
481
594
  try:
595
+ # Load active profile if configured
596
+ # Get project root (where .claude-mpm exists)
597
+ from pathlib import Path
598
+
482
599
  from ..core.shared.config_loader import ConfigLoader
483
- from ..services.agents.deployment.agent_deployment import AgentDeploymentService
484
600
  from ..services.agents.startup_sync import sync_agents_on_startup
485
601
  from ..services.profile_manager import ProfileManager
486
602
  from ..utils.progress import ProgressBar
487
603
 
488
- # Load active profile if configured
489
- # Get project root (where .claude-mpm exists)
490
- from pathlib import Path
491
604
  project_root = Path.cwd()
492
605
 
493
606
  profile_manager = ProfileManager(project_dir=project_root)
@@ -508,7 +621,7 @@ def sync_remote_agents_on_startup():
508
621
  )
509
622
 
510
623
  # Phase 1: Sync files from Git sources
511
- result = sync_agents_on_startup()
624
+ result = sync_agents_on_startup(force_refresh=force_sync)
512
625
 
513
626
  # Only proceed with deployment if sync was enabled and ran
514
627
  if result.get("enabled") and result.get("sources_synced", 0) > 0:
@@ -531,297 +644,95 @@ def sync_remote_agents_on_startup():
531
644
  logger.warning(f"Agent sync completed with {len(errors)} errors")
532
645
 
533
646
  # Phase 2: Deploy agents from cache to ~/.claude/agents/
534
- # This mirrors the skills deployment pattern (lines 371-407)
647
+ # Use reconciliation service to respect configuration.yaml settings
535
648
  try:
536
- # Initialize deployment service with profile-filtered configuration
537
- from ..core.config import Config
538
-
539
- deploy_config = None
540
- if active_profile and profile_manager.active_profile:
541
- # Create config with excluded agents based on profile
542
- # Get all agents that should be excluded (not in enabled list)
543
- from pathlib import Path
544
-
545
- cache_dir = Path.home() / ".claude-mpm" / "cache" / "agents"
546
- if cache_dir.exists():
547
- # Find all agent files
548
- all_agent_files = [
549
- f
550
- for f in cache_dir.rglob("*.md")
551
- if "/agents/" in str(f)
552
- and str(f).count("/agents/") == 1
553
- and f.stem.lower() != "base-agent"
554
- ]
555
-
556
- # Build exclusion list for agents not in profile
557
- excluded_agents = []
558
- for agent_file in all_agent_files:
559
- agent_name = agent_file.stem
560
- if not profile_manager.is_agent_enabled(agent_name):
561
- excluded_agents.append(agent_name)
562
-
563
- if excluded_agents:
564
- # Get singleton config and update with profile settings
565
- # BUGFIX: Config is a singleton that ignores dict parameter if already initialized.
566
- # Creating Config({...}) doesn't store excluded_agents - use set() instead.
567
- deploy_config = Config()
568
- deploy_config.set("agent_deployment.excluded_agents", excluded_agents)
569
- deploy_config.set("agent_deployment.filter_non_mpm_agents", False)
570
- deploy_config.set("agent_deployment.case_sensitive", False)
571
- deploy_config.set("agent_deployment.exclude_dependencies", False)
572
- logger.info(
573
- f"Profile '{active_profile}': Excluding {len(excluded_agents)} agents from deployment"
574
- )
575
-
576
- deployment_service = AgentDeploymentService(config=deploy_config)
577
-
578
- # Count agents in cache to show accurate progress
579
649
  from pathlib import Path
580
650
 
581
- cache_dir = Path.home() / ".claude-mpm" / "cache" / "agents"
582
- agent_count = 0
583
-
584
- if cache_dir.exists():
585
- # BUGFIX (cache-count-inflation): Clean up stale cache files
586
- # from old repositories before counting to prevent inflated counts.
587
- # Issue: Old caches like bobmatnyc/claude-mpm-agents/agents/
588
- # were counted alongside current agents, inflating count
589
- # from 44 to 85.
590
- #
591
- # Solution: Remove files with nested /agents/ paths
592
- # (e.g., cache/agents/user/repo/agents/...)
593
- # Keep only current agents (e.g., cache/agents/engineer/...)
594
- removed_count = 0
595
- stale_dirs = set()
596
-
597
- for md_file in cache_dir.rglob("*.md"):
598
- # Stale cache files have multiple /agents/ in their path
599
- # Current: ~/.claude-mpm/cache/agents/engineer/...
600
- # (1 occurrence)
601
- # Old: ~/.claude-mpm/cache/agents/bobmatnyc/.../agents/...
602
- # (2+ occurrences)
603
- if str(md_file).count("/agents/") > 1:
604
- # Track parent directory for cleanup
605
- # Extract subdirectory under cache/agents/
606
- # (e.g., "bobmatnyc")
607
- parts = md_file.parts
608
- cache_agents_idx = parts.index("agents")
609
- if cache_agents_idx + 1 < len(parts):
610
- stale_subdir = parts[cache_agents_idx + 1]
611
- # Only remove if it's not a known category directory
612
- if stale_subdir not in [
613
- "engineer",
614
- "ops",
615
- "qa",
616
- "universal",
617
- "documentation",
618
- "claude-mpm",
619
- "security",
620
- ]:
621
- stale_dirs.add(cache_dir / stale_subdir)
622
-
623
- md_file.unlink()
624
- removed_count += 1
625
-
626
- # Remove empty stale directories
627
- for stale_dir in stale_dirs:
628
- if stale_dir.exists() and stale_dir.is_dir():
629
- try:
630
- # Remove directory and all contents
631
- import shutil
632
-
633
- shutil.rmtree(stale_dir)
634
- except Exception:
635
- pass # Ignore cleanup errors
636
-
637
- if removed_count > 0:
638
- from loguru import logger
639
-
640
- logger.info(
641
- f"Cleaned up {removed_count} stale cache files "
642
- f"from old repositories"
643
- )
651
+ from ..core.unified_config import UnifiedConfig
652
+ from ..services.agents.deployment.startup_reconciliation import (
653
+ perform_startup_reconciliation,
654
+ )
644
655
 
645
- # Count MD files in cache (agent markdown files from
646
- # current repos)
647
- # BUGFIX: Only count files in agent directories,
648
- # not docs/templates/READMEs
649
- # Valid agent paths must contain "/agents/" exactly ONCE
650
- # (current structure)
651
- # Exclude PM templates, BASE-AGENT, and documentation files
652
- pm_templates = {
653
- "base-agent.md",
654
- "circuit_breakers.md",
655
- "pm_examples.md",
656
- "pm_red_flags.md",
657
- "research_gate_examples.md",
658
- "response_format.md",
659
- "ticket_completeness_examples.md",
660
- "validation_templates.md",
661
- "git_file_tracking.md",
662
- }
663
- # Documentation files to exclude (by filename)
664
- doc_files = {
665
- "readme.md",
666
- "changelog.md",
667
- "contributing.md",
668
- "implementation-summary.md",
669
- "reorganization-plan.md",
670
- "auto-deploy-index.md",
671
- }
672
-
673
- # Find all markdown files (after cleanup)
674
- all_md_files = list(cache_dir.rglob("*.md"))
675
-
676
- # Filter to only agent files:
677
- # 1. Must have "/agents/" in path exactly ONCE
678
- # (current structure)
679
- # 2. Must not be in PM templates or doc files
680
- # 3. Exclude BASE-AGENT.md which is not a deployable agent
681
- # 4. Exclude build artifacts (dist/, build/, .cache/)
682
- # to prevent double-counting
683
- agent_files = [
684
- f
685
- for f in all_md_files
686
- if (
687
- # Must be in an agent directory (from current
688
- # cache structure)
689
- "/agents/" in str(f)
690
- # NEW: Only ONE /agents/ in path (excludes old
691
- # nested repos)
692
- # This prevents counting old caches like
693
- # bobmatnyc/claude-mpm-agents/agents/...
694
- and str(f).count("/agents/") == 1
695
- # Exclude PM templates, doc files, and BASE-AGENT
696
- and f.name.lower() not in pm_templates
697
- and f.name.lower() not in doc_files
698
- and f.name.lower() != "base-agent.md"
699
- # Exclude build artifacts (prevents double-counting
700
- # source + built files)
701
- and not any(
702
- part in str(f).split("/")
703
- for part in ["dist", "build", ".cache"]
704
- )
705
- )
706
- ]
707
- agent_count = len(agent_files)
708
-
709
- if agent_count > 0:
710
- # Deploy agents to project-level directory where Claude Code expects them
711
- deploy_target = Path.cwd() / ".claude" / "agents"
712
- deployment_result = deployment_service.deploy_agents(
713
- target_dir=deploy_target,
714
- force_rebuild=False, # Only deploy if versions differ
715
- deployment_mode="update", # Version-aware updates
716
- config=deploy_config, # Pass config to respect profile filtering
656
+ # Load configuration
657
+ unified_config = UnifiedConfig()
658
+
659
+ # Override with profile settings if active
660
+ if active_profile and profile_manager.active_profile:
661
+ # Get enabled agents from profile (returns Set[str])
662
+ profile_enabled_agents = (
663
+ profile_manager.active_profile.get_enabled_agents()
664
+ )
665
+ # Update config with profile's enabled list (convert Set to List)
666
+ unified_config.agents.enabled = list(profile_enabled_agents)
667
+ logger.info(
668
+ f"Profile '{active_profile}': Using {len(profile_enabled_agents)} enabled agents"
717
669
  )
718
670
 
719
- # Get actual counts from deployment result (reflects configured agents)
720
- deployed = len(deployment_result.get("deployed", []))
721
- updated = len(deployment_result.get("updated", []))
722
- skipped = len(deployment_result.get("skipped", []))
723
- total_configured = deployed + updated + skipped
724
-
725
- # FALLBACK: If deployment result doesn't track skipped agents (async path),
726
- # count existing agents in target directory as "already deployed"
727
- # This ensures accurate reporting when agents are already up-to-date
728
- if total_configured == 0 and deploy_target.exists():
729
- existing_agents = list(deploy_target.glob("*.md"))
730
- # Filter out non-agent files (e.g., README.md, INSTRUCTIONS.md)
731
- agent_count_in_target = len(
732
- [
733
- f
734
- for f in existing_agents
735
- if not f.name.startswith(("README", "INSTRUCTIONS"))
736
- ]
737
- )
738
- if agent_count_in_target > 0:
739
- # All agents already deployed - count them as skipped
740
- skipped = agent_count_in_target
741
- total_configured = agent_count_in_target
671
+ # Perform reconciliation to deploy configured agents
672
+ project_path = Path.cwd()
673
+ agent_result, _skill_result = perform_startup_reconciliation(
674
+ project_path=project_path, config=unified_config, silent=False
675
+ )
676
+
677
+ # Display results with progress bar
678
+ total_operations = (
679
+ len(agent_result.deployed)
680
+ + len(agent_result.removed)
681
+ + len(agent_result.unchanged)
682
+ )
742
683
 
743
- # Create progress bar with actual configured agent count (not raw file count)
684
+ if total_operations > 0:
744
685
  deploy_progress = ProgressBar(
745
- total=total_configured if total_configured > 0 else 1,
686
+ total=total_operations,
746
687
  prefix="Deploying agents",
747
688
  show_percentage=True,
748
689
  show_counter=True,
749
690
  )
750
-
751
- # Update progress bar to completion
752
- deploy_progress.update(
753
- total_configured if total_configured > 0 else 1
754
- )
755
-
756
- # Cleanup orphaned agents (ours but no longer deployed)
757
- # Get list of deployed agent filenames (what should remain)
758
- deployed_filenames = []
759
- for agent_name in deployment_result.get("deployed", []):
760
- deployed_filenames.append(f"{agent_name}.md")
761
- for agent_name in deployment_result.get("updated", []):
762
- deployed_filenames.append(f"{agent_name}.md")
763
- for agent_name in deployment_result.get("skipped", []):
764
- deployed_filenames.append(f"{agent_name}.md")
765
-
766
- # Run cleanup and get count of removed agents
767
- removed = _cleanup_orphaned_agents(
768
- deploy_target, deployed_filenames
691
+ deploy_progress.update(total_operations)
692
+
693
+ # Build summary message
694
+ deployed = len(agent_result.deployed)
695
+ removed = len(agent_result.removed)
696
+ unchanged = len(agent_result.unchanged)
697
+
698
+ summary_parts = []
699
+ if deployed > 0:
700
+ summary_parts.append(f"{deployed} new")
701
+ if removed > 0:
702
+ summary_parts.append(f"{removed} removed")
703
+ if unchanged > 0:
704
+ summary_parts.append(f"{unchanged} unchanged")
705
+
706
+ summary = f"Complete: {', '.join(summary_parts)}"
707
+ deploy_progress.finish(summary)
708
+
709
+ # Display errors if any
710
+ if agent_result.errors:
711
+ logger.warning(
712
+ f"Agent deployment completed with {len(agent_result.errors)} errors"
769
713
  )
714
+ print("\n⚠️ Agent Deployment Errors:")
715
+ max_errors_to_show = 10
716
+ errors_to_display = agent_result.errors[:max_errors_to_show]
770
717
 
771
- # Show total configured agents (deployed + updated + already existing)
772
- # Include cache count for context and removed count if any
773
- if deployed > 0 or updated > 0:
774
- if removed > 0:
775
- deploy_progress.finish(
776
- f"Complete: {deployed} new, {updated} updated, {skipped} unchanged, "
777
- f"{removed} removed ({total_configured} configured from {agent_count} files in cache)"
778
- )
779
- else:
780
- deploy_progress.finish(
781
- f"Complete: {deployed} new, {updated} updated, {skipped} unchanged "
782
- f"({total_configured} configured from {agent_count} files in cache)"
783
- )
784
- elif removed > 0:
785
- deploy_progress.finish(
786
- f"Complete: {total_configured} agents deployed, "
787
- f"{removed} removed ({agent_count} files in cache)"
788
- )
789
- else:
790
- deploy_progress.finish(
791
- f"Complete: {total_configured} agents deployed "
792
- f"({agent_count} files in cache)"
793
- )
794
-
795
- # Display deployment errors to user (not just logs)
796
- deploy_errors = deployment_result.get("errors", [])
797
- if deploy_errors:
798
- # Log for debugging
799
- logger.warning(
800
- f"Agent deployment completed with {len(deploy_errors)} errors: {deploy_errors}"
801
- )
802
-
803
- # Display errors to user with clear formatting
804
- print("\n⚠️ Agent Deployment Errors:")
805
-
806
- # Show first 10 errors to avoid overwhelming output
807
- max_errors_to_show = 10
808
- errors_to_display = deploy_errors[:max_errors_to_show]
718
+ for error in errors_to_display:
719
+ print(f" - {error}")
809
720
 
810
- for error in errors_to_display:
811
- # Format error message for readability
812
- # Errors typically come as strings like "agent.md: Error message"
813
- print(f" - {error}")
721
+ if len(agent_result.errors) > max_errors_to_show:
722
+ remaining = len(agent_result.errors) - max_errors_to_show
723
+ print(f" ... and {remaining} more error(s)")
814
724
 
815
- # If more errors exist, show count
816
- if len(deploy_errors) > max_errors_to_show:
817
- remaining = len(deploy_errors) - max_errors_to_show
818
- print(f" ... and {remaining} more error(s)")
725
+ print(
726
+ f"\n❌ Failed to deploy {len(agent_result.errors)} agent(s). "
727
+ "Please check the error messages above."
728
+ )
729
+ print(" Run with --verbose for detailed error information.\n")
819
730
 
820
- # Show summary message
821
- print(
822
- f"\n❌ Failed to deploy {len(deploy_errors)} agent(s). Please check the error messages above."
823
- )
824
- print(" Run with --verbose for detailed error information.\n")
731
+ # Save deployment state to prevent duplicate deployment in ClaudeRunner
732
+ # This ensures setup_agents() skips deployment since we already reconciled
733
+ _save_deployment_state_after_reconciliation(
734
+ agent_result=agent_result, project_path=project_path
735
+ )
825
736
 
826
737
  except Exception as e:
827
738
  # Deployment failure shouldn't block startup
@@ -830,6 +741,11 @@ def sync_remote_agents_on_startup():
830
741
  logger = get_logger("cli")
831
742
  logger.warning(f"Failed to deploy agents from cache: {e}")
832
743
 
744
+ # Phase 4: Cleanup legacy agent cache directories (after sync/deployment)
745
+ # CRITICAL: This must run AFTER sync completes because sync may recreate
746
+ # legacy directories. Running cleanup here ensures they're removed.
747
+ cleanup_legacy_agent_cache()
748
+
833
749
  except Exception as e:
834
750
  # Non-critical - log but don't fail startup
835
751
  from ..core.logger import get_logger
@@ -838,8 +754,14 @@ def sync_remote_agents_on_startup():
838
754
  logger.debug(f"Failed to sync remote agents: {e}")
839
755
  # Continue execution - agent sync failure shouldn't block startup
840
756
 
757
+ # Cleanup legacy cache even if sync failed
758
+ try:
759
+ cleanup_legacy_agent_cache()
760
+ except Exception: # nosec B110
761
+ pass # Ignore cleanup errors
762
+
841
763
 
842
- def sync_remote_skills_on_startup():
764
+ def sync_remote_skills_on_startup(force_sync: bool = False):
843
765
  """
844
766
  Synchronize skill templates from remote sources on startup.
845
767
 
@@ -857,6 +779,9 @@ def sync_remote_skills_on_startup():
857
779
  4. Apply profile filtering if active
858
780
  5. Deploy resolved skills to ~/.claude/skills/ - Phase 2 progress bar
859
781
  6. Log deployment results with source indication
782
+
783
+ Args:
784
+ force_sync: Force download even if cache is fresh (bypasses ETag).
860
785
  """
861
786
  try:
862
787
  from pathlib import Path
@@ -962,7 +887,7 @@ def sync_remote_skills_on_startup():
962
887
 
963
888
  # Sync all sources with progress callback
964
889
  results = manager.sync_all_sources(
965
- force=False, progress_callback=sync_progress.update
890
+ force=force_sync, progress_callback=sync_progress.update
966
891
  )
967
892
 
968
893
  # Finish sync progress bar with clear breakdown
@@ -982,150 +907,191 @@ def sync_remote_skills_on_startup():
982
907
 
983
908
  # Phase 2: Scan agents and save to configuration.yaml
984
909
  # This step populates configuration.yaml with agent-referenced skills
985
- if results["synced_count"] > 0:
986
- agents_dir = Path.cwd() / ".claude" / "agents"
987
-
988
- # Scan agents for skill requirements
989
- agent_skills = get_required_skills_from_agents(agents_dir)
990
-
991
- # Save to project-level configuration.yaml
992
- project_config_path = Path.cwd() / ".claude-mpm" / "configuration.yaml"
993
- save_agent_skills_to_config(list(agent_skills), project_config_path)
994
-
995
- # Phase 3: Resolve which skills to deploy (user_defined or agent_referenced)
996
- skills_to_deploy, skill_source = get_skills_to_deploy(project_config_path)
997
-
998
- # Phase 4: Apply profile filtering if active
999
- if active_profile and profile_manager.active_profile:
1000
- # Filter skills based on profile
1001
- if skills_to_deploy:
1002
- # Filter the resolved skill list
1003
- original_count = len(skills_to_deploy)
1004
- filtered_skills = [
1005
- skill
1006
- for skill in skills_to_deploy
1007
- if profile_manager.is_skill_enabled(skill)
1008
- ]
1009
- filtered_count = original_count - len(filtered_skills)
910
+ # CRITICAL: Always scan agents to populate agent_referenced, even when using cached skills.
911
+ # Without this, skill_filter=None causes ALL skills to deploy and NO cleanup to run.
912
+ agents_dir = Path.cwd() / ".claude" / "agents"
913
+
914
+ # Scan agents for skill requirements (ALWAYS run to ensure cleanup works)
915
+ agent_skills = get_required_skills_from_agents(agents_dir)
916
+ logger.info(
917
+ f"Agent scan found {len(agent_skills)} unique skills across deployed agents"
918
+ )
1010
919
 
1011
- # SAFEGUARD: Warn if all skills were filtered out (misconfiguration)
1012
- if not filtered_skills and original_count > 0:
1013
- logger.warning(
1014
- f"Profile '{active_profile}' filtered ALL {original_count} skills. "
1015
- f"This may indicate a naming mismatch in the profile."
1016
- )
1017
- elif filtered_count > 0:
1018
- logger.info(
1019
- f"Profile '{active_profile}' filtered {filtered_count} skills "
1020
- f"({len(filtered_skills)} remaining)"
1021
- )
920
+ # Save to project-level configuration.yaml
921
+ project_config_path = Path.cwd() / ".claude-mpm" / "configuration.yaml"
922
+ save_agent_skills_to_config(list(agent_skills), project_config_path)
923
+ logger.debug(
924
+ f"Saved {len(agent_skills)} agent-referenced skills to {project_config_path}"
925
+ )
1022
926
 
1023
- skills_to_deploy = filtered_skills
1024
- skill_source = f"{skill_source} + profile filtered"
1025
- else:
1026
- # No explicit skill list - filter from all available
1027
- all_skills = manager.get_all_skills()
1028
- filtered_skills = [
1029
- skill["name"]
1030
- for skill in all_skills
1031
- if profile_manager.is_skill_enabled(skill["name"])
1032
- ]
1033
- skills_to_deploy = filtered_skills
1034
- skill_source = "profile filtered"
927
+ # Phase 3: Resolve which skills to deploy (user_defined or agent_referenced)
928
+ skills_to_deploy, skill_source = get_skills_to_deploy(project_config_path)
929
+
930
+ # CRITICAL DEBUG: Log deployment resolution to diagnose cleanup issues
931
+ if skills_to_deploy:
932
+ logger.info(
933
+ f"Resolved {len(skills_to_deploy)} skills from {skill_source} (cleanup will run)"
934
+ )
935
+ else:
936
+ logger.warning(
937
+ f"No skills resolved from {skill_source} - will deploy ALL skills WITHOUT cleanup! "
938
+ f"This may indicate agent_referenced is empty in configuration.yaml."
939
+ )
940
+
941
+ # Phase 4: Apply profile filtering if active
942
+ if active_profile and profile_manager.active_profile:
943
+ # Filter skills based on profile
944
+ if skills_to_deploy:
945
+ # Filter the resolved skill list
946
+ original_count = len(skills_to_deploy)
947
+ filtered_skills = [
948
+ skill
949
+ for skill in skills_to_deploy
950
+ if profile_manager.is_skill_enabled(skill)
951
+ ]
952
+ filtered_count = original_count - len(filtered_skills)
953
+
954
+ # SAFEGUARD: Warn if all skills were filtered out (misconfiguration)
955
+ if not filtered_skills and original_count > 0:
956
+ logger.warning(
957
+ f"Profile '{active_profile}' filtered ALL {original_count} skills. "
958
+ f"This may indicate a naming mismatch in the profile."
959
+ )
960
+ elif filtered_count > 0:
1035
961
  logger.info(
1036
- f"Profile '{active_profile}': "
1037
- f"{len(filtered_skills)} skills enabled from {len(all_skills)} available"
962
+ f"Profile '{active_profile}' filtered {filtered_count} skills "
963
+ f"({len(filtered_skills)} remaining)"
1038
964
  )
1039
965
 
1040
- # Get all skills to determine counts
1041
- all_skills = manager.get_all_skills()
1042
- total_skill_count = len(all_skills)
966
+ skills_to_deploy = filtered_skills
967
+ skill_source = f"{skill_source} + profile filtered"
968
+ else:
969
+ # No explicit skill list - filter from all available
970
+ all_skills = manager.get_all_skills()
971
+ filtered_skills = [
972
+ skill["name"]
973
+ for skill in all_skills
974
+ if profile_manager.is_skill_enabled(skill["name"])
975
+ ]
976
+ skills_to_deploy = filtered_skills
977
+ skill_source = "profile filtered"
978
+ logger.info(
979
+ f"Profile '{active_profile}': "
980
+ f"{len(filtered_skills)} skills enabled from {len(all_skills)} available"
981
+ )
1043
982
 
1044
- # Determine skill count based on resolution
1045
- skill_count = (
1046
- len(skills_to_deploy) if skills_to_deploy else total_skill_count
983
+ # Get all skills to determine counts
984
+ all_skills = manager.get_all_skills()
985
+ total_skill_count = len(all_skills)
986
+
987
+ # Determine skill count based on resolution
988
+ skill_count = len(skills_to_deploy) if skills_to_deploy else total_skill_count
989
+
990
+ if skill_count > 0:
991
+ # Deploy skills with resolved filter
992
+ # Deploy ONLY to project directory (not user-level)
993
+ # DESIGN DECISION: Project-level deployment keeps skills isolated per project,
994
+ # avoiding pollution of user's global ~/.claude/skills/ directory.
995
+
996
+ # Deploy to project-local directory with cleanup
997
+ deployment_result = manager.deploy_skills(
998
+ target_dir=Path.cwd() / ".claude" / "skills",
999
+ force=force_sync,
1000
+ # CRITICAL FIX: Empty list should mean "deploy no skills", not "deploy all"
1001
+ # When skills_to_deploy is [], we want skill_filter=set() NOT skill_filter=None
1002
+ # None means "no filtering" (deploy all), empty set means "filter to nothing"
1003
+ skill_filter=set(skills_to_deploy)
1004
+ if skills_to_deploy is not None
1005
+ else None,
1047
1006
  )
1048
1007
 
1049
- if skill_count > 0:
1050
- # Deploy skills with resolved filter
1051
- # Deploy to project directory (like agents), not user directory
1052
- deployment_result = manager.deploy_skills(
1053
- target_dir=Path.cwd() / ".claude" / "skills",
1054
- force=False,
1055
- skill_filter=set(skills_to_deploy) if skills_to_deploy else None,
1008
+ # REMOVED: User-level deployment (lines 1068-1074)
1009
+ # Reason: Skills should be project-specific, not user-global.
1010
+ # Claude Code can read from project-level .claude/skills/ directory.
1011
+
1012
+ # Get actual counts from deployment result (use project-local for display)
1013
+ deployed = deployment_result.get("deployed_count", 0)
1014
+ skipped = deployment_result.get("skipped_count", 0)
1015
+ filtered = deployment_result.get("filtered_count", 0)
1016
+ removed = deployment_result.get("removed_count", 0)
1017
+ total_available = deployed + skipped
1018
+
1019
+ # Only show progress bar if there are skills to deploy
1020
+ if total_available > 0:
1021
+ deploy_progress = ProgressBar(
1022
+ total=total_available,
1023
+ prefix="Deploying skill directories",
1024
+ show_percentage=True,
1025
+ show_counter=True,
1056
1026
  )
1027
+ # Update progress bar to completion
1028
+ deploy_progress.update(total_available)
1029
+ else:
1030
+ # No skills to deploy - create dummy progress for message only
1031
+ deploy_progress = ProgressBar(
1032
+ total=1,
1033
+ prefix="Deploying skill directories",
1034
+ show_percentage=False,
1035
+ show_counter=False,
1036
+ )
1037
+ deploy_progress.update(1)
1057
1038
 
1058
- # Get actual counts from deployment result
1059
- deployed = deployment_result.get("deployed_count", 0)
1060
- skipped = deployment_result.get("skipped_count", 0)
1061
- filtered = deployment_result.get("filtered_count", 0)
1062
- total_available = deployed + skipped
1039
+ # Show total available skills (deployed + already existing)
1040
+ # Include source indication (user_defined vs agent_referenced)
1041
+ # Note: total_skill_count is from cache, total_available is what's deployed/needed
1042
+ source_label = (
1043
+ "user override" if skill_source == "user_defined" else "from agents"
1044
+ )
1063
1045
 
1064
- # Only show progress bar if there are skills to deploy
1065
- if total_available > 0:
1066
- deploy_progress = ProgressBar(
1067
- total=total_available,
1068
- prefix="Deploying skill directories",
1069
- show_percentage=True,
1070
- show_counter=True,
1071
- )
1072
- # Update progress bar to completion
1073
- deploy_progress.update(total_available)
1074
- else:
1075
- # No skills to deploy - create dummy progress for message only
1076
- deploy_progress = ProgressBar(
1077
- total=1,
1078
- prefix="Deploying skill directories",
1079
- show_percentage=False,
1080
- show_counter=False,
1081
- )
1082
- deploy_progress.update(1)
1046
+ # Build finish message with cleanup info
1047
+ if deployed > 0 or removed > 0:
1048
+ parts = []
1049
+ if deployed > 0:
1050
+ parts.append(f"{deployed} new")
1051
+ if skipped > 0:
1052
+ parts.append(f"{skipped} unchanged")
1053
+ if removed > 0:
1054
+ parts.append(f"{removed} removed")
1083
1055
 
1084
- # Show total available skills (deployed + already existing)
1085
- # Include source indication (user_defined vs agent_referenced)
1086
- # Note: total_skill_count is from cache, total_available is what's deployed/needed
1087
- source_label = (
1088
- "user override" if skill_source == "user_defined" else "from agents"
1089
- )
1056
+ status = ", ".join(parts)
1090
1057
 
1091
- if deployed > 0:
1092
- if filtered > 0:
1093
- deploy_progress.finish(
1094
- f"Complete: {deployed} new, {skipped} unchanged "
1095
- f"({total_available} {source_label}, {filtered} files in cache)"
1096
- )
1097
- else:
1098
- deploy_progress.finish(
1099
- f"Complete: {deployed} new, {skipped} unchanged "
1100
- f"({total_available} skills {source_label} from {total_skill_count} files in cache)"
1101
- )
1102
- elif filtered > 0:
1103
- # Skills filtered means agents require fewer skills than available
1058
+ if filtered > 0:
1104
1059
  deploy_progress.finish(
1105
- f"No skills needed ({source_label}, {total_skill_count} files in cache)"
1060
+ f"Complete: {status} ({total_available} {source_label}, {filtered} files in cache)"
1106
1061
  )
1107
1062
  else:
1108
1063
  deploy_progress.finish(
1109
- f"Complete: {total_available} skills {source_label} "
1110
- f"({total_skill_count} files in cache)"
1064
+ f"Complete: {status} ({total_available} skills {source_label} from {total_skill_count} files in cache)"
1111
1065
  )
1066
+ elif filtered > 0:
1067
+ # Skills filtered means agents require fewer skills than available
1068
+ deploy_progress.finish(
1069
+ f"No skills needed ({source_label}, {total_skill_count} files in cache)"
1070
+ )
1071
+ else:
1072
+ # No changes - all skills already deployed
1073
+ msg = f"Complete: {total_available} skills {source_label}"
1074
+ if removed > 0:
1075
+ msg += f", {removed} removed"
1076
+ msg += f" ({total_skill_count} files in cache)"
1077
+ deploy_progress.finish(msg)
1078
+
1079
+ # Log deployment errors if any
1080
+ from ..core.logger import get_logger
1112
1081
 
1113
- # Log deployment errors if any
1114
- from ..core.logger import get_logger
1115
-
1116
- logger = get_logger("cli")
1082
+ logger = get_logger("cli")
1117
1083
 
1118
- errors = deployment_result.get("errors", [])
1119
- if errors:
1120
- logger.warning(
1121
- f"Skill deployment completed with {len(errors)} errors: {errors}"
1122
- )
1084
+ errors = deployment_result.get("errors", [])
1085
+ if errors:
1086
+ logger.warning(
1087
+ f"Skill deployment completed with {len(errors)} errors: {errors}"
1088
+ )
1123
1089
 
1124
- # Log sync errors if any
1125
- if results["failed_count"] > 0:
1126
- logger.warning(
1127
- f"Skill sync completed with {results['failed_count']} failures"
1128
- )
1090
+ # Log sync errors if any
1091
+ if results["failed_count"] > 0:
1092
+ logger.warning(
1093
+ f"Skill sync completed with {results['failed_count']} failures"
1094
+ )
1129
1095
 
1130
1096
  except Exception as e:
1131
1097
  # Non-critical - log but don't fail startup
@@ -1208,7 +1174,7 @@ def show_agent_summary():
1208
1174
  # Display summary if we have agents
1209
1175
  if installed_count > 0 or available_count > 0:
1210
1176
  print(
1211
- f"✓ Agents: {installed_count} deployed / {available_count} cached",
1177
+ f"✓ Agents: {installed_count} deployed / {max(0, available_count - installed_count)} cached",
1212
1178
  flush=True,
1213
1179
  )
1214
1180
 
@@ -1225,61 +1191,64 @@ def show_skill_summary():
1225
1191
  Display skill availability summary on startup.
1226
1192
 
1227
1193
  WHY: Users should see at a glance how many skills are deployed and available
1228
- from collections, similar to the agent summary.
1194
+ from cache, similar to the agent summary showing "X deployed / Y cached".
1229
1195
 
1230
- DESIGN DECISION: Fast, non-blocking check that counts skills from deployment
1231
- directory and collection repos. Shows "X installed (Y available)" format.
1196
+ DESIGN DECISION: Fast, non-blocking check that counts skills from:
1197
+ - Deployed skills: PROJECT-level .claude/skills/ directory
1198
+ - Cached skills: ~/.claude-mpm/cache/skills/ directory (from remote sources)
1199
+
1200
+ Shows format: "✓ Skills: X deployed / Y cached"
1232
1201
  Failures are silent to avoid blocking startup.
1233
1202
  """
1234
1203
  try:
1235
1204
  from pathlib import Path
1236
1205
 
1237
- # Count deployed skills (installed)
1238
- skills_dir = Path.home() / ".claude" / "skills"
1239
- installed_count = 0
1240
- if skills_dir.exists():
1206
+ # Count deployed skills (PROJECT-level, not user-level)
1207
+ project_skills_dir = Path.cwd() / ".claude" / "skills"
1208
+ deployed_count = 0
1209
+ if project_skills_dir.exists():
1241
1210
  # Count directories with SKILL.md (excludes collection repos)
1242
1211
  # Exclude collection directories (obra-superpowers, etc.)
1243
1212
  skill_dirs = [
1244
1213
  d
1245
- for d in skills_dir.iterdir()
1214
+ for d in project_skills_dir.iterdir()
1246
1215
  if d.is_dir()
1247
1216
  and (d / "SKILL.md").exists()
1248
1217
  and not (d / ".git").exists() # Exclude collection repos
1249
1218
  ]
1250
- installed_count = len(skill_dirs)
1219
+ deployed_count = len(skill_dirs)
1251
1220
 
1252
- # Count available skills in collections
1253
- available_count = 0
1254
- if skills_dir.exists():
1255
- # Scan all collection directories (those with .git)
1256
- for collection_dir in skills_dir.iterdir():
1257
- if (
1258
- not collection_dir.is_dir()
1259
- or not (collection_dir / ".git").exists()
1260
- ):
1221
+ # Count cached skills (from remote sources, not deployed yet)
1222
+ # This matches the agent summary pattern: deployed vs cached
1223
+ cache_dir = Path.home() / ".claude-mpm" / "cache" / "skills"
1224
+ cached_count = 0
1225
+ if cache_dir.exists():
1226
+ # Scan all repository directories in cache
1227
+ # Cache structure: ~/.claude-mpm/cache/skills/{owner}/{repo}/...
1228
+ for repo_dir in cache_dir.rglob("*"):
1229
+ if not repo_dir.is_dir():
1261
1230
  continue
1262
1231
 
1263
- # Count skill directories in this collection
1232
+ # Count skill directories (those with SKILL.md)
1264
1233
  # Skills can be nested in: skills/category/skill-name/SKILL.md
1265
1234
  # or in flat structure: skill-name/SKILL.md
1266
- for root, dirs, files in os.walk(collection_dir):
1235
+ for root, dirs, files in os.walk(repo_dir):
1267
1236
  if "SKILL.md" in files:
1268
- # Exclude build artifacts and hidden directories (within the collection)
1269
- # Get relative path from collection_dir to avoid excluding based on .claude parent
1237
+ # Exclude build artifacts and hidden directories
1270
1238
  root_path = Path(root)
1271
- relative_parts = root_path.relative_to(collection_dir).parts
1272
1239
  if not any(
1273
1240
  part.startswith(".")
1274
1241
  or part in ["dist", "build", "__pycache__"]
1275
- for part in relative_parts
1242
+ for part in root_path.parts
1276
1243
  ):
1277
- available_count += 1
1244
+ cached_count += 1
1278
1245
 
1279
- # Display summary if we have skills
1280
- if installed_count > 0 or available_count > 0:
1246
+ # Display summary using agent summary format: "X deployed / Y cached"
1247
+ # Only show non-deployed cached skills (subtract deployed from cached)
1248
+ non_deployed_cached = max(0, cached_count - deployed_count)
1249
+ if deployed_count > 0 or non_deployed_cached > 0:
1281
1250
  print(
1282
- f"✓ Skills: {installed_count} installed ({available_count} available)",
1251
+ f"✓ Skills: {deployed_count} deployed / {non_deployed_cached} cached",
1283
1252
  flush=True,
1284
1253
  )
1285
1254
 
@@ -1292,33 +1261,70 @@ def show_skill_summary():
1292
1261
 
1293
1262
 
1294
1263
  def verify_and_show_pm_skills():
1295
- """Verify PM skills and display status.
1264
+ """Verify PM skills and display status with enhanced validation.
1296
1265
 
1297
- WHY: PM skills are essential for PM agent operation.
1298
- Shows deployment status and auto-deploys if missing.
1266
+ WHY: PM skills are CRITICAL for PM agent operation. PM must KNOW if
1267
+ framework knowledge is unavailable at startup. Enhanced validation
1268
+ checks all required skills exist, are not corrupted, and auto-repairs
1269
+ if needed.
1270
+
1271
+ Shows deployment status:
1272
+ - "✓ PM skills: 8/8 verified" if all required skills are valid
1273
+ - "⚠ PM skills: 2 missing, auto-repairing..." if issues detected
1274
+ - Non-blocking but visible warning if auto-repair fails
1299
1275
  """
1300
1276
  try:
1301
1277
  from pathlib import Path
1302
1278
 
1303
- from ..services.pm_skills_deployer import PMSkillsDeployerService
1279
+ from ..services.pm_skills_deployer import (
1280
+ REQUIRED_PM_SKILLS,
1281
+ PMSkillsDeployerService,
1282
+ )
1304
1283
 
1305
1284
  deployer = PMSkillsDeployerService()
1306
1285
  project_dir = Path.cwd()
1307
1286
 
1308
- result = deployer.verify_pm_skills(project_dir)
1287
+ # Verify with auto-repair enabled
1288
+ result = deployer.verify_pm_skills(project_dir, auto_repair=True)
1309
1289
 
1310
1290
  if result.verified:
1311
- # Show verified status
1312
- print(f"✓ PM skills: {result.skill_count} verified", flush=True)
1291
+ # Show verified status with count
1292
+ total_required = len(REQUIRED_PM_SKILLS)
1293
+ print(
1294
+ f"✓ PM skills: {total_required}/{total_required} verified", flush=True
1295
+ )
1313
1296
  else:
1314
- # Auto-deploy if missing
1315
- print("Deploying PM skills...", end="", flush=True)
1316
- deploy_result = deployer.deploy_pm_skills(project_dir)
1317
- if deploy_result.success:
1318
- total = len(deploy_result.deployed) + len(deploy_result.skipped)
1319
- print(f"\r✓ PM skills: {total} deployed" + " " * 20, flush=True)
1297
+ # Show warning with details
1298
+ missing_count = len(result.missing_skills)
1299
+ corrupted_count = len(result.corrupted_skills)
1300
+
1301
+ # Build status message
1302
+ issues = []
1303
+ if missing_count > 0:
1304
+ issues.append(f"{missing_count} missing")
1305
+ if corrupted_count > 0:
1306
+ issues.append(f"{corrupted_count} corrupted")
1307
+
1308
+ status = ", ".join(issues)
1309
+
1310
+ # Check if auto-repair was attempted
1311
+ if "Auto-repaired" in result.message:
1312
+ # Auto-repair succeeded
1313
+ total_required = len(REQUIRED_PM_SKILLS)
1314
+ print(
1315
+ f"✓ PM skills: {total_required}/{total_required} verified (auto-repaired)",
1316
+ flush=True,
1317
+ )
1320
1318
  else:
1321
- print(f"\r⚠ PM skills: deployment failed" + " " * 20, flush=True)
1319
+ # Auto-repair failed or not attempted
1320
+ print(f"⚠ PM skills: {status}", flush=True)
1321
+
1322
+ # Log warnings for debugging
1323
+ from ..core.logger import get_logger
1324
+
1325
+ logger = get_logger("cli")
1326
+ for warning in result.warnings:
1327
+ logger.warning(f"PM skills: {warning}")
1322
1328
 
1323
1329
  except ImportError:
1324
1330
  # PM skills deployer not available - skip silently
@@ -1353,7 +1359,7 @@ def auto_install_chrome_devtools_on_startup():
1353
1359
  if not chrome_devtools_config.get("auto_install", True):
1354
1360
  # Auto-install disabled, skip silently
1355
1361
  return
1356
- except Exception:
1362
+ except Exception: # nosec B110
1357
1363
  # If config loading fails, assume auto-install is enabled (default)
1358
1364
  pass
1359
1365
 
@@ -1371,7 +1377,7 @@ def auto_install_chrome_devtools_on_startup():
1371
1377
  # Continue execution - chrome-devtools installation failure shouldn't block startup
1372
1378
 
1373
1379
 
1374
- def run_background_services():
1380
+ def run_background_services(force_sync: bool = False):
1375
1381
  """
1376
1382
  Initialize all background services on startup.
1377
1383
 
@@ -1382,6 +1388,9 @@ def run_background_services():
1382
1388
  explicitly requests them via agent-manager commands. This prevents unwanted
1383
1389
  file creation in project .claude/ directories.
1384
1390
  See: SystemInstructionsDeployer and agent_deployment.py line 504-509
1391
+
1392
+ Args:
1393
+ force_sync: Force download even if cache is fresh (bypasses ETag).
1385
1394
  """
1386
1395
  # Sync hooks early to ensure up-to-date configuration
1387
1396
  # RATIONALE: Hooks should be synced before other services to fix stale configs
@@ -1392,7 +1401,9 @@ def run_background_services():
1392
1401
  check_mcp_auto_configuration()
1393
1402
  verify_mcp_gateway_startup()
1394
1403
  check_for_updates_async()
1395
- sync_remote_agents_on_startup() # Sync agents from remote sources
1404
+ sync_remote_agents_on_startup(
1405
+ force_sync=force_sync
1406
+ ) # Sync agents from remote sources
1396
1407
  show_agent_summary() # Display agent counts after deployment
1397
1408
 
1398
1409
  # Skills deployment order (precedence: remote > bundled)
@@ -1401,7 +1412,9 @@ def run_background_services():
1401
1412
  # 3. Discover and link runtime skills (user-added skills)
1402
1413
  # This ensures remote skills take precedence over bundled skills when names conflict
1403
1414
  deploy_bundled_skills() # Base layer: package-bundled skills
1404
- sync_remote_skills_on_startup() # Override layer: Git-based skills (takes precedence)
1415
+ sync_remote_skills_on_startup(
1416
+ force_sync=force_sync
1417
+ ) # Override layer: Git-based skills (takes precedence)
1405
1418
  discover_and_link_runtime_skills() # Discovery: user-added skills
1406
1419
  show_skill_summary() # Display skill counts after deployment
1407
1420
  verify_and_show_pm_skills() # PM skills verification and status
@@ -1514,11 +1527,19 @@ def check_mcp_auto_configuration():
1514
1527
  check_and_configure_mcp()
1515
1528
 
1516
1529
  # Clear the "Checking..." message by overwriting with spaces
1517
- print("\r" + " " * 30 + "\r", end="", flush=True)
1530
+ # Only use carriage return clearing if stdout is a real TTY
1531
+ if sys.stdout.isatty():
1532
+ print("\r" + " " * 30 + "\r", end="", flush=True)
1533
+ else:
1534
+ print() # Simple newline for non-TTY (like Claude Code REPL)
1518
1535
 
1519
1536
  except Exception as e:
1520
1537
  # Clear progress message on error
1521
- print("\r" + " " * 30 + "\r", end="", flush=True)
1538
+ # Only use carriage return clearing if stdout is a real TTY
1539
+ if sys.stdout.isatty():
1540
+ print("\r" + " " * 30 + "\r", end="", flush=True)
1541
+ else:
1542
+ print() # Simple newline for non-TTY (like Claude Code REPL)
1522
1543
 
1523
1544
  # Non-critical - log but don't fail
1524
1545
  from ..core.logger import get_logger
@@ -1601,7 +1622,7 @@ def verify_mcp_gateway_startup():
1601
1622
  loop.run_until_complete(
1602
1623
  asyncio.gather(*pending, return_exceptions=True)
1603
1624
  )
1604
- except Exception:
1625
+ except Exception: # nosec B110
1605
1626
  pass # Ignore cleanup errors
1606
1627
  finally:
1607
1628
  loop.close()
@@ -1695,7 +1716,7 @@ def check_for_updates_async():
1695
1716
 
1696
1717
  logger = get_logger("upgrade_check")
1697
1718
  logger.debug(f"Update check failed (non-critical): {e}")
1698
- except Exception:
1719
+ except Exception: # nosec B110
1699
1720
  pass # Avoid any errors in error handling
1700
1721
  finally:
1701
1722
  # Properly clean up event loop
@@ -1710,7 +1731,7 @@ def check_for_updates_async():
1710
1731
  loop.run_until_complete(
1711
1732
  asyncio.gather(*pending, return_exceptions=True)
1712
1733
  )
1713
- except Exception:
1734
+ except Exception: # nosec B110
1714
1735
  pass # Ignore cleanup errors
1715
1736
  finally:
1716
1737
  loop.close()