claude-mpm 5.4.48__py3-none-any.whl → 5.6.34__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of claude-mpm might be problematic. Click here for more details.

Files changed (467) 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 +119 -689
  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 +171 -17
  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 +5 -0
  33. claude_mpm/cli/startup.py +544 -511
  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 +39 -13
  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 +66 -18
  154. claude_mpm/core/shared/config_loader.py +3 -1
  155. claude_mpm/core/socketio_pool.py +47 -15
  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 +527 -136
  239. claude_mpm/hooks/claude_hooks/hook_handler.py +170 -104
  240. claude_mpm/hooks/claude_hooks/hook_wrapper.sh +6 -11
  241. claude_mpm/hooks/claude_hooks/installer.py +206 -36
  242. claude_mpm/hooks/claude_hooks/memory_integration.py +52 -32
  243. claude_mpm/hooks/claude_hooks/response_tracking.py +43 -60
  244. claude_mpm/hooks/claude_hooks/services/__init__.py +21 -0
  245. claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-311.pyc +0 -0
  246. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-311.pyc +0 -0
  247. claude_mpm/hooks/claude_hooks/services/__pycache__/container.cpython-311.pyc +0 -0
  248. claude_mpm/hooks/claude_hooks/services/__pycache__/protocols.cpython-311.pyc +0 -0
  249. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-311.pyc +0 -0
  250. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-311.pyc +0 -0
  251. claude_mpm/hooks/claude_hooks/services/connection_manager.py +41 -26
  252. claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +38 -105
  253. claude_mpm/hooks/claude_hooks/services/container.py +310 -0
  254. claude_mpm/hooks/claude_hooks/services/protocols.py +328 -0
  255. claude_mpm/hooks/claude_hooks/services/state_manager.py +25 -38
  256. claude_mpm/hooks/claude_hooks/services/subagent_processor.py +75 -77
  257. claude_mpm/hooks/session_resume_hook.py +89 -1
  258. claude_mpm/hooks/templates/pre_tool_use_simple.py +6 -6
  259. claude_mpm/hooks/templates/pre_tool_use_template.py +16 -8
  260. claude_mpm/init.py +215 -2
  261. claude_mpm/scripts/claude-hook-handler.sh +46 -19
  262. claude_mpm/scripts/start_activity_logging.py +0 -0
  263. claude_mpm/services/agents/agent_recommendation_service.py +8 -8
  264. claude_mpm/services/agents/agent_selection_service.py +2 -2
  265. claude_mpm/services/agents/cache_git_manager.py +1 -1
  266. claude_mpm/services/agents/deployment/agent_discovery_service.py +3 -1
  267. claude_mpm/services/agents/deployment/agent_format_converter.py +8 -6
  268. claude_mpm/services/agents/deployment/agent_template_builder.py +14 -4
  269. claude_mpm/services/agents/deployment/deployment_reconciler.py +577 -0
  270. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +4 -4
  271. claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +4 -1
  272. claude_mpm/services/agents/deployment/startup_reconciliation.py +138 -0
  273. claude_mpm/services/agents/git_source_manager.py +6 -2
  274. claude_mpm/services/agents/loading/framework_agent_loader.py +75 -2
  275. claude_mpm/services/agents/single_tier_deployment_service.py +4 -4
  276. claude_mpm/services/agents/sources/git_source_sync_service.py +10 -5
  277. claude_mpm/services/agents/startup_sync.py +5 -2
  278. claude_mpm/services/cli/__init__.py +3 -0
  279. claude_mpm/services/cli/incremental_pause_manager.py +561 -0
  280. claude_mpm/services/cli/session_resume_helper.py +10 -2
  281. claude_mpm/services/command_deployment_service.py +44 -26
  282. claude_mpm/services/delegation_detector.py +175 -0
  283. claude_mpm/services/diagnostics/checks/agent_sources_check.py +30 -0
  284. claude_mpm/services/diagnostics/checks/configuration_check.py +24 -0
  285. claude_mpm/services/diagnostics/checks/installation_check.py +22 -0
  286. claude_mpm/services/diagnostics/checks/mcp_services_check.py +23 -0
  287. claude_mpm/services/diagnostics/doctor_reporter.py +31 -1
  288. claude_mpm/services/diagnostics/models.py +14 -1
  289. claude_mpm/services/event_log.py +325 -0
  290. claude_mpm/services/hook_installer_service.py +77 -8
  291. claude_mpm/services/infrastructure/__init__.py +4 -0
  292. claude_mpm/services/infrastructure/context_usage_tracker.py +291 -0
  293. claude_mpm/services/infrastructure/resume_log_generator.py +24 -5
  294. claude_mpm/services/monitor/daemon_manager.py +15 -4
  295. claude_mpm/services/monitor/management/lifecycle.py +8 -3
  296. claude_mpm/services/monitor/server.py +106 -16
  297. claude_mpm/services/pm_skills_deployer.py +267 -94
  298. claude_mpm/services/profile_manager.py +10 -4
  299. claude_mpm/services/skills/git_skill_source_manager.py +192 -29
  300. claude_mpm/services/skills/selective_skill_deployer.py +211 -46
  301. claude_mpm/services/skills/skill_discovery_service.py +74 -4
  302. claude_mpm/services/skills_deployer.py +188 -67
  303. claude_mpm/services/socketio/handlers/hook.py +14 -7
  304. claude_mpm/services/socketio/server/main.py +12 -4
  305. claude_mpm/skills/__init__.py +2 -1
  306. claude_mpm/skills/bundled/collaboration/brainstorming/SKILL.md +79 -0
  307. claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/SKILL.md +178 -0
  308. claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/agent-prompts.md +577 -0
  309. claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/coordination-patterns.md +467 -0
  310. claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/examples.md +537 -0
  311. claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/troubleshooting.md +730 -0
  312. claude_mpm/skills/bundled/collaboration/git-worktrees.md +317 -0
  313. claude_mpm/skills/bundled/collaboration/requesting-code-review/SKILL.md +112 -0
  314. claude_mpm/skills/bundled/collaboration/requesting-code-review/references/code-reviewer-template.md +146 -0
  315. claude_mpm/skills/bundled/collaboration/requesting-code-review/references/review-examples.md +412 -0
  316. claude_mpm/skills/bundled/collaboration/stacked-prs.md +251 -0
  317. claude_mpm/skills/bundled/collaboration/writing-plans/SKILL.md +81 -0
  318. claude_mpm/skills/bundled/collaboration/writing-plans/references/best-practices.md +362 -0
  319. claude_mpm/skills/bundled/collaboration/writing-plans/references/plan-structure-templates.md +312 -0
  320. claude_mpm/skills/bundled/debugging/root-cause-tracing/SKILL.md +152 -0
  321. claude_mpm/skills/bundled/debugging/root-cause-tracing/references/advanced-techniques.md +668 -0
  322. claude_mpm/skills/bundled/debugging/root-cause-tracing/references/examples.md +587 -0
  323. claude_mpm/skills/bundled/debugging/root-cause-tracing/references/integration.md +438 -0
  324. claude_mpm/skills/bundled/debugging/root-cause-tracing/references/tracing-techniques.md +391 -0
  325. claude_mpm/skills/bundled/debugging/systematic-debugging/CREATION-LOG.md +119 -0
  326. claude_mpm/skills/bundled/debugging/systematic-debugging/SKILL.md +148 -0
  327. claude_mpm/skills/bundled/debugging/systematic-debugging/references/anti-patterns.md +483 -0
  328. claude_mpm/skills/bundled/debugging/systematic-debugging/references/examples.md +452 -0
  329. claude_mpm/skills/bundled/debugging/systematic-debugging/references/troubleshooting.md +449 -0
  330. claude_mpm/skills/bundled/debugging/systematic-debugging/references/workflow.md +411 -0
  331. claude_mpm/skills/bundled/debugging/systematic-debugging/test-academic.md +14 -0
  332. claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-1.md +58 -0
  333. claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-2.md +68 -0
  334. claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-3.md +69 -0
  335. claude_mpm/skills/bundled/debugging/verification-before-completion/SKILL.md +131 -0
  336. claude_mpm/skills/bundled/debugging/verification-before-completion/references/gate-function.md +325 -0
  337. claude_mpm/skills/bundled/debugging/verification-before-completion/references/integration-and-workflows.md +490 -0
  338. claude_mpm/skills/bundled/debugging/verification-before-completion/references/red-flags-and-failures.md +425 -0
  339. claude_mpm/skills/bundled/debugging/verification-before-completion/references/verification-patterns.md +499 -0
  340. claude_mpm/skills/bundled/infrastructure/env-manager/INTEGRATION.md +611 -0
  341. claude_mpm/skills/bundled/infrastructure/env-manager/README.md +596 -0
  342. claude_mpm/skills/bundled/infrastructure/env-manager/SKILL.md +260 -0
  343. claude_mpm/skills/bundled/infrastructure/env-manager/examples/nextjs-env-structure.md +315 -0
  344. claude_mpm/skills/bundled/infrastructure/env-manager/references/frameworks.md +436 -0
  345. claude_mpm/skills/bundled/infrastructure/env-manager/references/security.md +433 -0
  346. claude_mpm/skills/bundled/infrastructure/env-manager/references/synchronization.md +452 -0
  347. claude_mpm/skills/bundled/infrastructure/env-manager/references/troubleshooting.md +404 -0
  348. claude_mpm/skills/bundled/infrastructure/env-manager/references/validation.md +420 -0
  349. claude_mpm/skills/bundled/main/artifacts-builder/SKILL.md +86 -0
  350. claude_mpm/skills/bundled/main/internal-comms/SKILL.md +43 -0
  351. claude_mpm/skills/bundled/main/internal-comms/examples/3p-updates.md +47 -0
  352. claude_mpm/skills/bundled/main/internal-comms/examples/company-newsletter.md +65 -0
  353. claude_mpm/skills/bundled/main/internal-comms/examples/faq-answers.md +30 -0
  354. claude_mpm/skills/bundled/main/internal-comms/examples/general-comms.md +16 -0
  355. claude_mpm/skills/bundled/main/mcp-builder/SKILL.md +160 -0
  356. claude_mpm/skills/bundled/main/mcp-builder/reference/design_principles.md +412 -0
  357. claude_mpm/skills/bundled/main/mcp-builder/reference/evaluation.md +602 -0
  358. claude_mpm/skills/bundled/main/mcp-builder/reference/mcp_best_practices.md +915 -0
  359. claude_mpm/skills/bundled/main/mcp-builder/reference/node_mcp_server.md +916 -0
  360. claude_mpm/skills/bundled/main/mcp-builder/reference/python_mcp_server.md +752 -0
  361. claude_mpm/skills/bundled/main/mcp-builder/reference/workflow.md +1237 -0
  362. claude_mpm/skills/bundled/main/skill-creator/SKILL.md +189 -0
  363. claude_mpm/skills/bundled/main/skill-creator/references/best-practices.md +500 -0
  364. claude_mpm/skills/bundled/main/skill-creator/references/creation-workflow.md +464 -0
  365. claude_mpm/skills/bundled/main/skill-creator/references/examples.md +619 -0
  366. claude_mpm/skills/bundled/main/skill-creator/references/progressive-disclosure.md +437 -0
  367. claude_mpm/skills/bundled/main/skill-creator/references/skill-structure.md +231 -0
  368. claude_mpm/skills/bundled/php/espocrm-development/SKILL.md +170 -0
  369. claude_mpm/skills/bundled/php/espocrm-development/references/architecture.md +602 -0
  370. claude_mpm/skills/bundled/php/espocrm-development/references/common-tasks.md +821 -0
  371. claude_mpm/skills/bundled/php/espocrm-development/references/development-workflow.md +742 -0
  372. claude_mpm/skills/bundled/php/espocrm-development/references/frontend-customization.md +726 -0
  373. claude_mpm/skills/bundled/php/espocrm-development/references/hooks-and-services.md +764 -0
  374. claude_mpm/skills/bundled/php/espocrm-development/references/testing-debugging.md +831 -0
  375. claude_mpm/skills/bundled/pm/mpm/SKILL.md +38 -0
  376. claude_mpm/skills/bundled/pm/mpm-agent-update-workflow/SKILL.md +75 -0
  377. claude_mpm/skills/bundled/pm/mpm-bug-reporting/SKILL.md +248 -0
  378. claude_mpm/skills/bundled/pm/mpm-circuit-breaker-enforcement/SKILL.md +476 -0
  379. claude_mpm/skills/bundled/pm/mpm-config/SKILL.md +29 -0
  380. claude_mpm/skills/bundled/pm/mpm-delegation-patterns/SKILL.md +167 -0
  381. claude_mpm/skills/bundled/pm/mpm-doctor/SKILL.md +53 -0
  382. claude_mpm/skills/bundled/pm/mpm-git-file-tracking/SKILL.md +113 -0
  383. claude_mpm/skills/bundled/pm/mpm-help/SKILL.md +35 -0
  384. claude_mpm/skills/bundled/pm/mpm-init/SKILL.md +125 -0
  385. claude_mpm/skills/bundled/pm/mpm-monitor/SKILL.md +32 -0
  386. claude_mpm/skills/bundled/pm/mpm-organize/SKILL.md +121 -0
  387. claude_mpm/skills/bundled/pm/mpm-postmortem/SKILL.md +22 -0
  388. claude_mpm/skills/bundled/pm/mpm-pr-workflow/SKILL.md +124 -0
  389. claude_mpm/skills/bundled/pm/mpm-session-management/SKILL.md +312 -0
  390. claude_mpm/skills/bundled/pm/mpm-session-pause/SKILL.md +170 -0
  391. claude_mpm/skills/bundled/pm/mpm-session-resume/SKILL.md +31 -0
  392. claude_mpm/skills/bundled/pm/mpm-status/SKILL.md +37 -0
  393. claude_mpm/skills/bundled/pm/mpm-teaching-mode/SKILL.md +657 -0
  394. claude_mpm/skills/bundled/pm/mpm-ticket-view/SKILL.md +110 -0
  395. claude_mpm/skills/bundled/pm/mpm-ticketing-integration/SKILL.md +154 -0
  396. claude_mpm/skills/bundled/pm/mpm-tool-usage-guide/SKILL.md +386 -0
  397. claude_mpm/skills/bundled/pm/mpm-verification-protocols/SKILL.md +198 -0
  398. claude_mpm/skills/bundled/pm/mpm-version/SKILL.md +21 -0
  399. claude_mpm/skills/bundled/react/flexlayout-react.md +742 -0
  400. claude_mpm/skills/bundled/rust/desktop-applications/SKILL.md +226 -0
  401. claude_mpm/skills/bundled/rust/desktop-applications/references/architecture-patterns.md +901 -0
  402. claude_mpm/skills/bundled/rust/desktop-applications/references/native-gui-frameworks.md +901 -0
  403. claude_mpm/skills/bundled/rust/desktop-applications/references/platform-integration.md +775 -0
  404. claude_mpm/skills/bundled/rust/desktop-applications/references/state-management.md +937 -0
  405. claude_mpm/skills/bundled/rust/desktop-applications/references/tauri-framework.md +770 -0
  406. claude_mpm/skills/bundled/rust/desktop-applications/references/testing-deployment.md +961 -0
  407. claude_mpm/skills/bundled/security-scanning.md +112 -0
  408. claude_mpm/skills/bundled/tauri/tauri-async-patterns.md +495 -0
  409. claude_mpm/skills/bundled/tauri/tauri-build-deploy.md +599 -0
  410. claude_mpm/skills/bundled/tauri/tauri-command-patterns.md +535 -0
  411. claude_mpm/skills/bundled/tauri/tauri-error-handling.md +613 -0
  412. claude_mpm/skills/bundled/tauri/tauri-event-system.md +648 -0
  413. claude_mpm/skills/bundled/tauri/tauri-file-system.md +673 -0
  414. claude_mpm/skills/bundled/tauri/tauri-frontend-integration.md +767 -0
  415. claude_mpm/skills/bundled/tauri/tauri-performance.md +669 -0
  416. claude_mpm/skills/bundled/tauri/tauri-state-management.md +573 -0
  417. claude_mpm/skills/bundled/tauri/tauri-testing.md +384 -0
  418. claude_mpm/skills/bundled/tauri/tauri-window-management.md +628 -0
  419. claude_mpm/skills/bundled/testing/condition-based-waiting/SKILL.md +119 -0
  420. claude_mpm/skills/bundled/testing/condition-based-waiting/references/patterns-and-implementation.md +253 -0
  421. claude_mpm/skills/bundled/testing/test-driven-development/SKILL.md +145 -0
  422. claude_mpm/skills/bundled/testing/test-driven-development/references/anti-patterns.md +543 -0
  423. claude_mpm/skills/bundled/testing/test-driven-development/references/examples.md +741 -0
  424. claude_mpm/skills/bundled/testing/test-driven-development/references/integration.md +470 -0
  425. claude_mpm/skills/bundled/testing/test-driven-development/references/philosophy.md +458 -0
  426. claude_mpm/skills/bundled/testing/test-driven-development/references/workflow.md +639 -0
  427. claude_mpm/skills/bundled/testing/test-quality-inspector/SKILL.md +458 -0
  428. claude_mpm/skills/bundled/testing/test-quality-inspector/examples/example-inspection-report.md +411 -0
  429. claude_mpm/skills/bundled/testing/test-quality-inspector/references/assertion-quality.md +317 -0
  430. claude_mpm/skills/bundled/testing/test-quality-inspector/references/inspection-checklist.md +270 -0
  431. claude_mpm/skills/bundled/testing/test-quality-inspector/references/red-flags.md +436 -0
  432. claude_mpm/skills/bundled/testing/testing-anti-patterns/SKILL.md +140 -0
  433. claude_mpm/skills/bundled/testing/testing-anti-patterns/references/completeness-anti-patterns.md +572 -0
  434. claude_mpm/skills/bundled/testing/testing-anti-patterns/references/core-anti-patterns.md +411 -0
  435. claude_mpm/skills/bundled/testing/testing-anti-patterns/references/detection-guide.md +569 -0
  436. claude_mpm/skills/bundled/testing/testing-anti-patterns/references/tdd-connection.md +695 -0
  437. claude_mpm/skills/bundled/testing/webapp-testing/SKILL.md +184 -0
  438. claude_mpm/skills/bundled/testing/webapp-testing/decision-tree.md +459 -0
  439. claude_mpm/skills/bundled/testing/webapp-testing/playwright-patterns.md +479 -0
  440. claude_mpm/skills/bundled/testing/webapp-testing/reconnaissance-pattern.md +687 -0
  441. claude_mpm/skills/bundled/testing/webapp-testing/server-management.md +758 -0
  442. claude_mpm/skills/bundled/testing/webapp-testing/troubleshooting.md +868 -0
  443. claude_mpm/skills/registry.py +295 -90
  444. claude_mpm/skills/skill_manager.py +29 -23
  445. claude_mpm/templates/.pre-commit-config.yaml +112 -0
  446. claude_mpm/utils/agent_dependency_loader.py +103 -4
  447. claude_mpm/utils/robust_installer.py +45 -24
  448. claude_mpm-5.6.34.dist-info/METADATA +393 -0
  449. {claude_mpm-5.4.48.dist-info → claude_mpm-5.6.34.dist-info}/RECORD +453 -151
  450. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.B_FtCwCQ.css +0 -1
  451. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.Cl_eSA4x.css +0 -1
  452. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BgChzWQ1.js +0 -1
  453. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CIXEwuWe.js +0 -1
  454. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CWc5urbQ.js +0 -1
  455. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DMkZpdF2.js +0 -2
  456. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/app.DTL5mJO-.js +0 -2
  457. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.DzuEhzqh.js +0 -1
  458. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/1.DFLC8jdE.js +0 -1
  459. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.DPvEihJJ.js +0 -10
  460. claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-311.pyc +0 -0
  461. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager.cpython-311.pyc +0 -0
  462. claude_mpm-5.4.48.dist-info/METADATA +0 -999
  463. {claude_mpm-5.4.48.dist-info → claude_mpm-5.6.34.dist-info}/WHEEL +0 -0
  464. {claude_mpm-5.4.48.dist-info → claude_mpm-5.6.34.dist-info}/entry_points.txt +0 -0
  465. {claude_mpm-5.4.48.dist-info → claude_mpm-5.6.34.dist-info}/licenses/LICENSE +0 -0
  466. {claude_mpm-5.4.48.dist-info → claude_mpm-5.6.34.dist-info}/licenses/LICENSE-FAQ.md +0 -0
  467. {claude_mpm-5.4.48.dist-info → claude_mpm-5.6.34.dist-info}/top_level.txt +0 -0
claude_mpm/cli/startup.py CHANGED
@@ -34,13 +34,13 @@ def sync_hooks_on_startup(quiet: bool = False) -> bool:
34
34
  installer = HookInstaller()
35
35
 
36
36
  # Show brief status (hooks sync is fast)
37
- if not quiet:
37
+ if not quiet and sys.stdout.isatty():
38
38
  print("Syncing Claude Code hooks...", end=" ", flush=True)
39
39
 
40
40
  # Reinstall hooks (force=True ensures update)
41
41
  success = installer.install_hooks(force=True)
42
42
 
43
- if not quiet:
43
+ if not quiet and sys.stdout.isatty():
44
44
  if success:
45
45
  print("✓")
46
46
  else:
@@ -49,7 +49,7 @@ def sync_hooks_on_startup(quiet: bool = False) -> bool:
49
49
  return success
50
50
 
51
51
  except Exception as e:
52
- if not quiet:
52
+ if not quiet and sys.stdout.isatty():
53
53
  print("(error)")
54
54
  # Log but don't fail startup
55
55
  from ..core.logger import get_logger
@@ -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
 
@@ -252,11 +280,13 @@ def deploy_bundled_skills():
252
280
  if deployment_result.get("deployed"):
253
281
  # Show simple feedback for deployed skills
254
282
  deployed_count = len(deployment_result["deployed"])
255
- print(f"✓ Bundled skills ready ({deployed_count} deployed)", flush=True)
283
+ if sys.stdout.isatty():
284
+ print(f"✓ Bundled skills ready ({deployed_count} deployed)", flush=True)
256
285
  logger.info(f"Skills: Deployed {deployed_count} skill(s)")
257
286
  elif not deployment_result.get("errors"):
258
287
  # No deployment needed, skills already present
259
- print("✓ Bundled skills ready", flush=True)
288
+ if sys.stdout.isatty():
289
+ print("✓ Bundled skills ready", flush=True)
260
290
 
261
291
  if deployment_result.get("errors"):
262
292
  logger.warning(
@@ -290,7 +320,8 @@ def discover_and_link_runtime_skills():
290
320
 
291
321
  discover_skills()
292
322
  # Show simple success feedback
293
- print("✓ Runtime skills linked", flush=True)
323
+ if sys.stdout.isatty():
324
+ print("✓ Runtime skills linked", flush=True)
294
325
  except Exception as e:
295
326
  # Import logger here to avoid circular imports
296
327
  from ..core.logger import get_logger
@@ -308,63 +339,62 @@ def deploy_output_style_on_startup():
308
339
  communication without emojis and exclamation points. Styles are project-specific
309
340
  to allow different projects to have different communication styles.
310
341
 
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.
342
+ DESIGN DECISION: This is non-blocking and idempotent. Deploys to user-level
343
+ directory (~/.claude/output-styles/) which is the official Claude Code location
344
+ for custom output styles.
314
345
 
315
- Deploys two styles:
316
- - claude-mpm-style.md (professional mode)
346
+ Deploys all styles:
347
+ - claude-mpm.md (professional mode)
317
348
  - claude-mpm-teacher.md (teaching mode)
349
+ - claude-mpm-research.md (research mode - for codebase analysis)
318
350
  """
319
351
  try:
320
- import shutil
321
- from pathlib import Path
352
+ from ..core.output_style_manager import OutputStyleManager
322
353
 
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"
354
+ # Initialize the output style manager
355
+ manager = OutputStyleManager()
327
356
 
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
- )
357
+ # Check if Claude Code version supports output styles (>= 1.0.83)
358
+ if not manager.supports_output_styles():
359
+ # Skip deployment for older versions
360
+ # The manager will fall back to injecting content directly
361
+ return
344
362
 
345
- if already_deployed:
363
+ # Check if all styles are already deployed and up-to-date
364
+ all_up_to_date = True
365
+ for style_config in manager.styles.values():
366
+ source_path = style_config["source"]
367
+ target_path = style_config["target"]
368
+
369
+ if not (
370
+ target_path.exists()
371
+ and source_path.exists()
372
+ and target_path.stat().st_size == source_path.stat().st_size
373
+ ):
374
+ all_up_to_date = False
375
+ break
376
+
377
+ if all_up_to_date:
346
378
  # Show feedback that output styles are ready
347
- print("✓ Output styles ready", flush=True)
379
+ if sys.stdout.isatty():
380
+ print("✓ Output styles ready", flush=True)
348
381
  return
349
382
 
350
- # Deploy both styles
351
- deployed_count = 0
352
- if professional_source.exists():
353
- shutil.copy2(professional_source, professional_target)
354
- deployed_count += 1
383
+ # Deploy all styles using the manager
384
+ results = manager.deploy_all_styles(activate_default=True)
355
385
 
356
- if teacher_source.exists():
357
- shutil.copy2(teacher_source, teacher_target)
358
- deployed_count += 1
386
+ # Count successful deployments
387
+ deployed_count = sum(1 for success in results.values() if success)
359
388
 
360
389
  if deployed_count > 0:
361
- print(f"✓ Output styles deployed ({deployed_count} styles)", flush=True)
390
+ if sys.stdout.isatty():
391
+ print(f"✓ Output styles deployed ({deployed_count} styles)", flush=True)
362
392
  else:
363
- # Source files missing - log but don't fail
393
+ # Deployment failed - log but don't fail startup
364
394
  from ..core.logger import get_logger
365
395
 
366
396
  logger = get_logger("cli")
367
- logger.debug("Output style source files not found")
397
+ logger.debug("Failed to deploy any output styles")
368
398
 
369
399
  except Exception as e:
370
400
  # Non-critical - log but don't fail startup
@@ -453,7 +483,95 @@ def _cleanup_orphaned_agents(deploy_target: Path, deployed_agents: list[str]) ->
453
483
  return removed_count
454
484
 
455
485
 
456
- def sync_remote_agents_on_startup():
486
+ def _save_deployment_state_after_reconciliation(
487
+ agent_result, project_path: Path
488
+ ) -> None:
489
+ """Save deployment state after reconciliation to prevent duplicate deployment.
490
+
491
+ WHY: After perform_startup_reconciliation() deploys agents to .claude/agents/,
492
+ we need to save a deployment state file so that ClaudeRunner.setup_agents()
493
+ can detect agents are already deployed and skip redundant deployment.
494
+
495
+ This prevents the "✓ Deployed 31 native agents" duplicate deployment that
496
+ occurs when setup_agents() doesn't know reconciliation already ran.
497
+
498
+ Args:
499
+ agent_result: DeploymentResult from perform_startup_reconciliation()
500
+ project_path: Project root directory
501
+
502
+ DESIGN DECISION: Use same state file format as ClaudeRunner._save_deployment_state()
503
+ Located at: .claude-mpm/cache/deployment_state.json
504
+
505
+ State file format:
506
+ {
507
+ "version": "5.6.13",
508
+ "agent_count": 15,
509
+ "deployment_hash": "sha256:...",
510
+ "deployed_at": 1234567890.123
511
+ }
512
+ """
513
+ import hashlib
514
+ import json
515
+ import time
516
+
517
+ from ..core.logger import get_logger
518
+
519
+ logger = get_logger("cli")
520
+
521
+ try:
522
+ # Get version from package
523
+ from claude_mpm import __version__
524
+
525
+ # Path to state file (matches ClaudeRunner._get_deployment_state_path())
526
+ state_file = project_path / ".claude-mpm" / "cache" / "deployment_state.json"
527
+ agents_dir = project_path / ".claude" / "agents"
528
+
529
+ # Count deployed agents
530
+ if agents_dir.exists():
531
+ agent_count = len(list(agents_dir.glob("*.md")))
532
+ else:
533
+ agent_count = 0
534
+
535
+ # Calculate deployment hash (matches ClaudeRunner._calculate_deployment_hash())
536
+ # CRITICAL: Must match exact hash algorithm used in ClaudeRunner
537
+ # Hashes filename + file content (not mtime) for consistency
538
+ deployment_hash = ""
539
+ if agents_dir.exists():
540
+ agent_files = sorted(agents_dir.glob("*.md"))
541
+ hash_obj = hashlib.sha256()
542
+ for agent_file in agent_files:
543
+ # Include filename and content in hash (matches ClaudeRunner)
544
+ hash_obj.update(agent_file.name.encode())
545
+ try:
546
+ hash_obj.update(agent_file.read_bytes())
547
+ except Exception as e:
548
+ logger.debug(f"Error reading {agent_file} for hash: {e}")
549
+
550
+ deployment_hash = hash_obj.hexdigest()
551
+
552
+ # Create state data
553
+ state_data = {
554
+ "version": __version__,
555
+ "agent_count": agent_count,
556
+ "deployment_hash": deployment_hash,
557
+ "deployed_at": time.time(),
558
+ }
559
+
560
+ # Ensure directory exists
561
+ state_file.parent.mkdir(parents=True, exist_ok=True)
562
+
563
+ # Write state file
564
+ state_file.write_text(json.dumps(state_data, indent=2))
565
+ logger.debug(
566
+ f"Saved deployment state after reconciliation: {agent_count} agents"
567
+ )
568
+
569
+ except Exception as e:
570
+ # Non-critical error - log but don't fail startup
571
+ logger.debug(f"Failed to save deployment state: {e}")
572
+
573
+
574
+ def sync_remote_agents_on_startup(force_sync: bool = False):
457
575
  """
458
576
  Synchronize agent templates from remote sources on startup.
459
577
 
@@ -466,28 +584,28 @@ def sync_remote_agents_on_startup():
466
584
  block startup to ensure claude-mpm remains functional.
467
585
 
468
586
  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
587
+ 1. Sync all enabled Git sources (download/cache files) - Phase 1 progress bar
588
+ 2. Deploy agents to ~/.claude/agents/ - Phase 2 progress bar
589
+ 3. Cleanup orphaned agents (ours but no longer deployed) - Phase 3
590
+ 4. Cleanup legacy agent cache directories (after sync/deployment) - Phase 4
473
591
  5. Log deployment results
474
- """
475
- # Cleanup legacy cache directories first (before syncing)
476
- cleanup_legacy_agent_cache()
477
592
 
478
- # DEPRECATED: Legacy warning - replaced by automatic cleanup above
593
+ Args:
594
+ force_sync: Force download even if cache is fresh (bypasses ETag).
595
+ """
596
+ # DEPRECATED: Legacy warning - no-op function, kept for compatibility
479
597
  check_legacy_cache()
480
598
 
481
599
  try:
600
+ # Load active profile if configured
601
+ # Get project root (where .claude-mpm exists)
602
+ from pathlib import Path
603
+
482
604
  from ..core.shared.config_loader import ConfigLoader
483
- from ..services.agents.deployment.agent_deployment import AgentDeploymentService
484
605
  from ..services.agents.startup_sync import sync_agents_on_startup
485
606
  from ..services.profile_manager import ProfileManager
486
607
  from ..utils.progress import ProgressBar
487
608
 
488
- # Load active profile if configured
489
- # Get project root (where .claude-mpm exists)
490
- from pathlib import Path
491
609
  project_root = Path.cwd()
492
610
 
493
611
  profile_manager = ProfileManager(project_dir=project_root)
@@ -508,7 +626,7 @@ def sync_remote_agents_on_startup():
508
626
  )
509
627
 
510
628
  # Phase 1: Sync files from Git sources
511
- result = sync_agents_on_startup()
629
+ result = sync_agents_on_startup(force_refresh=force_sync)
512
630
 
513
631
  # Only proceed with deployment if sync was enabled and ran
514
632
  if result.get("enabled") and result.get("sources_synced", 0) > 0:
@@ -531,294 +649,95 @@ def sync_remote_agents_on_startup():
531
649
  logger.warning(f"Agent sync completed with {len(errors)} errors")
532
650
 
533
651
  # Phase 2: Deploy agents from cache to ~/.claude/agents/
534
- # This mirrors the skills deployment pattern (lines 371-407)
652
+ # Use reconciliation service to respect configuration.yaml settings
535
653
  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
- # Supports both flat cache and {owner}/{repo}/agents/ structure
549
- all_agent_files = [
550
- f
551
- for f in cache_dir.rglob("*.md")
552
- if "/agents/" in str(f)
553
- and f.stem.lower() != "base-agent"
554
- and f.name.lower() not in {"readme.md", "changelog.md", "contributing.md"}
555
- ]
556
-
557
- # Build exclusion list for agents not in profile
558
- excluded_agents = []
559
- for agent_file in all_agent_files:
560
- agent_name = agent_file.stem
561
- if not profile_manager.is_agent_enabled(agent_name):
562
- excluded_agents.append(agent_name)
563
-
564
- if excluded_agents:
565
- # Get singleton config and update with profile settings
566
- # BUGFIX: Config is a singleton that ignores dict parameter if already initialized.
567
- # Creating Config({...}) doesn't store excluded_agents - use set() instead.
568
- deploy_config = Config()
569
- deploy_config.set("agent_deployment.excluded_agents", excluded_agents)
570
- deploy_config.set("agent_deployment.filter_non_mpm_agents", False)
571
- deploy_config.set("agent_deployment.case_sensitive", False)
572
- deploy_config.set("agent_deployment.exclude_dependencies", False)
573
- logger.info(
574
- f"Profile '{active_profile}': Excluding {len(excluded_agents)} agents from deployment"
575
- )
576
-
577
- deployment_service = AgentDeploymentService(config=deploy_config)
578
-
579
- # Count agents in cache to show accurate progress
580
654
  from pathlib import Path
581
655
 
582
- cache_dir = Path.home() / ".claude-mpm" / "cache" / "agents"
583
- agent_count = 0
584
-
585
- if cache_dir.exists():
586
- # BUGFIX (cache-count-inflation): Clean up stale cache files
587
- # from old repositories before counting to prevent inflated counts.
588
- # Issue: Old caches like bobmatnyc/claude-mpm-agents/agents/
589
- # were counted alongside current agents, inflating count
590
- # from 44 to 85.
591
- #
592
- # Solution: Remove files with nested /agents/ paths
593
- # (e.g., cache/agents/user/repo/agents/...)
594
- # Keep only current agents (e.g., cache/agents/engineer/...)
595
- removed_count = 0
596
- stale_dirs = set()
597
-
598
- for md_file in cache_dir.rglob("*.md"):
599
- # Stale cache files have multiple /agents/ in their path
600
- # Current: ~/.claude-mpm/cache/agents/engineer/...
601
- # (1 occurrence)
602
- # Old: ~/.claude-mpm/cache/agents/bobmatnyc/.../agents/...
603
- # (2+ occurrences)
604
- if str(md_file).count("/agents/") > 1:
605
- # Track parent directory for cleanup
606
- # Extract subdirectory under cache/agents/
607
- # (e.g., "bobmatnyc")
608
- parts = md_file.parts
609
- cache_agents_idx = parts.index("agents")
610
- if cache_agents_idx + 1 < len(parts):
611
- stale_subdir = parts[cache_agents_idx + 1]
612
- # Only remove if it's not a known category directory
613
- if stale_subdir not in [
614
- "engineer",
615
- "ops",
616
- "qa",
617
- "universal",
618
- "documentation",
619
- "claude-mpm",
620
- "security",
621
- ]:
622
- stale_dirs.add(cache_dir / stale_subdir)
623
-
624
- md_file.unlink()
625
- removed_count += 1
626
-
627
- # Remove empty stale directories
628
- for stale_dir in stale_dirs:
629
- if stale_dir.exists() and stale_dir.is_dir():
630
- try:
631
- # Remove directory and all contents
632
- import shutil
633
-
634
- shutil.rmtree(stale_dir)
635
- except Exception:
636
- pass # Ignore cleanup errors
637
-
638
- if removed_count > 0:
639
- from loguru import logger
640
-
641
- logger.info(
642
- f"Cleaned up {removed_count} stale cache files "
643
- f"from old repositories"
644
- )
656
+ from ..core.unified_config import UnifiedConfig
657
+ from ..services.agents.deployment.startup_reconciliation import (
658
+ perform_startup_reconciliation,
659
+ )
645
660
 
646
- # Count MD files in cache (agent markdown files from
647
- # current repos)
648
- # BUGFIX: Only count files in agent directories,
649
- # not docs/templates/READMEs
650
- # Valid agent paths must contain "/agents/" exactly ONCE
651
- # (current structure)
652
- # Exclude PM templates, BASE-AGENT, and documentation files
653
- pm_templates = {
654
- "base-agent.md",
655
- "circuit_breakers.md",
656
- "pm_examples.md",
657
- "pm_red_flags.md",
658
- "research_gate_examples.md",
659
- "response_format.md",
660
- "ticket_completeness_examples.md",
661
- "validation_templates.md",
662
- "git_file_tracking.md",
663
- }
664
- # Documentation files to exclude (by filename)
665
- doc_files = {
666
- "readme.md",
667
- "changelog.md",
668
- "contributing.md",
669
- "implementation-summary.md",
670
- "reorganization-plan.md",
671
- "auto-deploy-index.md",
672
- }
673
-
674
- # Find all markdown files (after cleanup)
675
- all_md_files = list(cache_dir.rglob("*.md"))
676
-
677
- # Filter to only agent files:
678
- # 1. Must have "/agents/" in path (current structure supports
679
- # both flat and {owner}/{repo}/agents/ patterns)
680
- # 2. Must not be in PM templates or doc files
681
- # 3. Exclude BASE-AGENT.md which is not a deployable agent
682
- # 4. Exclude build artifacts (dist/, build/, .cache/)
683
- # to prevent double-counting
684
- agent_files = [
685
- f
686
- for f in all_md_files
687
- if (
688
- # Must be in an agent directory
689
- # Supports: cache/agents/{category}/... (flat)
690
- # Supports: cache/agents/{owner}/{repo}/agents/{category}/... (GitHub sync)
691
- "/agents/" in str(f)
692
- # Exclude PM templates, doc files, and BASE-AGENT
693
- and f.name.lower() not in pm_templates
694
- and f.name.lower() not in doc_files
695
- and f.name.lower() != "base-agent.md"
696
- # Exclude build artifacts (prevents double-counting
697
- # source + built files)
698
- and not any(
699
- part in str(f).split("/")
700
- for part in ["dist", "build", ".cache"]
701
- )
702
- )
703
- ]
704
- agent_count = len(agent_files)
705
-
706
- if agent_count > 0:
707
- # Deploy agents to project-level directory where Claude Code expects them
708
- deploy_target = Path.cwd() / ".claude" / "agents"
709
- deployment_result = deployment_service.deploy_agents(
710
- target_dir=deploy_target,
711
- force_rebuild=False, # Only deploy if versions differ
712
- deployment_mode="update", # Version-aware updates
713
- config=deploy_config, # Pass config to respect profile filtering
661
+ # Load configuration
662
+ unified_config = UnifiedConfig()
663
+
664
+ # Override with profile settings if active
665
+ if active_profile and profile_manager.active_profile:
666
+ # Get enabled agents from profile (returns Set[str])
667
+ profile_enabled_agents = (
668
+ profile_manager.active_profile.get_enabled_agents()
669
+ )
670
+ # Update config with profile's enabled list (convert Set to List)
671
+ unified_config.agents.enabled = list(profile_enabled_agents)
672
+ logger.info(
673
+ f"Profile '{active_profile}': Using {len(profile_enabled_agents)} enabled agents"
714
674
  )
715
675
 
716
- # Get actual counts from deployment result (reflects configured agents)
717
- deployed = len(deployment_result.get("deployed", []))
718
- updated = len(deployment_result.get("updated", []))
719
- skipped = len(deployment_result.get("skipped", []))
720
- total_configured = deployed + updated + skipped
721
-
722
- # FALLBACK: If deployment result doesn't track skipped agents (async path),
723
- # count existing agents in target directory as "already deployed"
724
- # This ensures accurate reporting when agents are already up-to-date
725
- if total_configured == 0 and deploy_target.exists():
726
- existing_agents = list(deploy_target.glob("*.md"))
727
- # Filter out non-agent files (e.g., README.md, INSTRUCTIONS.md)
728
- agent_count_in_target = len(
729
- [
730
- f
731
- for f in existing_agents
732
- if not f.name.startswith(("README", "INSTRUCTIONS"))
733
- ]
734
- )
735
- if agent_count_in_target > 0:
736
- # All agents already deployed - count them as skipped
737
- skipped = agent_count_in_target
738
- total_configured = agent_count_in_target
676
+ # Perform reconciliation to deploy configured agents
677
+ project_path = Path.cwd()
678
+ agent_result, _skill_result = perform_startup_reconciliation(
679
+ project_path=project_path, config=unified_config, silent=False
680
+ )
681
+
682
+ # Display results with progress bar
683
+ total_operations = (
684
+ len(agent_result.deployed)
685
+ + len(agent_result.removed)
686
+ + len(agent_result.unchanged)
687
+ )
739
688
 
740
- # Create progress bar with actual configured agent count (not raw file count)
689
+ if total_operations > 0:
741
690
  deploy_progress = ProgressBar(
742
- total=total_configured if total_configured > 0 else 1,
691
+ total=total_operations,
743
692
  prefix="Deploying agents",
744
693
  show_percentage=True,
745
694
  show_counter=True,
746
695
  )
747
-
748
- # Update progress bar to completion
749
- deploy_progress.update(
750
- total_configured if total_configured > 0 else 1
696
+ deploy_progress.update(total_operations)
697
+
698
+ # Build summary message
699
+ deployed = len(agent_result.deployed)
700
+ removed = len(agent_result.removed)
701
+ unchanged = len(agent_result.unchanged)
702
+
703
+ summary_parts = []
704
+ if deployed > 0:
705
+ summary_parts.append(f"{deployed} new")
706
+ if removed > 0:
707
+ summary_parts.append(f"{removed} removed")
708
+ if unchanged > 0:
709
+ summary_parts.append(f"{unchanged} unchanged")
710
+
711
+ summary = f"Complete: {', '.join(summary_parts)}"
712
+ deploy_progress.finish(summary)
713
+
714
+ # Display errors if any
715
+ if agent_result.errors:
716
+ logger.warning(
717
+ f"Agent deployment completed with {len(agent_result.errors)} errors"
751
718
  )
719
+ print("\n⚠️ Agent Deployment Errors:")
720
+ max_errors_to_show = 10
721
+ errors_to_display = agent_result.errors[:max_errors_to_show]
752
722
 
753
- # Cleanup orphaned agents (ours but no longer deployed)
754
- # Get list of deployed agent filenames (what should remain)
755
- deployed_filenames = []
756
- for agent_name in deployment_result.get("deployed", []):
757
- deployed_filenames.append(f"{agent_name}.md")
758
- for agent_name in deployment_result.get("updated", []):
759
- deployed_filenames.append(f"{agent_name}.md")
760
- for agent_name in deployment_result.get("skipped", []):
761
- deployed_filenames.append(f"{agent_name}.md")
762
-
763
- # Run cleanup and get count of removed agents
764
- removed = _cleanup_orphaned_agents(
765
- deploy_target, deployed_filenames
766
- )
723
+ for error in errors_to_display:
724
+ print(f" - {error}")
767
725
 
768
- # Show total configured agents (deployed + updated + already existing)
769
- # Include cache count for context and removed count if any
770
- if deployed > 0 or updated > 0:
771
- if removed > 0:
772
- deploy_progress.finish(
773
- f"Complete: {deployed} new, {updated} updated, {skipped} unchanged, "
774
- f"{removed} removed ({total_configured} configured from {agent_count} files in cache)"
775
- )
776
- else:
777
- deploy_progress.finish(
778
- f"Complete: {deployed} new, {updated} updated, {skipped} unchanged "
779
- f"({total_configured} configured from {agent_count} files in cache)"
780
- )
781
- elif removed > 0:
782
- deploy_progress.finish(
783
- f"Complete: {total_configured} agents deployed, "
784
- f"{removed} removed ({agent_count} files in cache)"
785
- )
786
- else:
787
- deploy_progress.finish(
788
- f"Complete: {total_configured} agents deployed "
789
- f"({agent_count} files in cache)"
790
- )
726
+ if len(agent_result.errors) > max_errors_to_show:
727
+ remaining = len(agent_result.errors) - max_errors_to_show
728
+ print(f" ... and {remaining} more error(s)")
791
729
 
792
- # Display deployment errors to user (not just logs)
793
- deploy_errors = deployment_result.get("errors", [])
794
- if deploy_errors:
795
- # Log for debugging
796
- logger.warning(
797
- f"Agent deployment completed with {len(deploy_errors)} errors: {deploy_errors}"
798
- )
799
-
800
- # Display errors to user with clear formatting
801
- print("\n⚠️ Agent Deployment Errors:")
802
-
803
- # Show first 10 errors to avoid overwhelming output
804
- max_errors_to_show = 10
805
- errors_to_display = deploy_errors[:max_errors_to_show]
806
-
807
- for error in errors_to_display:
808
- # Format error message for readability
809
- # Errors typically come as strings like "agent.md: Error message"
810
- print(f" - {error}")
811
-
812
- # If more errors exist, show count
813
- if len(deploy_errors) > max_errors_to_show:
814
- remaining = len(deploy_errors) - max_errors_to_show
815
- print(f" ... and {remaining} more error(s)")
730
+ print(
731
+ f"\n❌ Failed to deploy {len(agent_result.errors)} agent(s). "
732
+ "Please check the error messages above."
733
+ )
734
+ print(" Run with --verbose for detailed error information.\n")
816
735
 
817
- # Show summary message
818
- print(
819
- f"\n❌ Failed to deploy {len(deploy_errors)} agent(s). Please check the error messages above."
820
- )
821
- print(" Run with --verbose for detailed error information.\n")
736
+ # Save deployment state to prevent duplicate deployment in ClaudeRunner
737
+ # This ensures setup_agents() skips deployment since we already reconciled
738
+ _save_deployment_state_after_reconciliation(
739
+ agent_result=agent_result, project_path=project_path
740
+ )
822
741
 
823
742
  except Exception as e:
824
743
  # Deployment failure shouldn't block startup
@@ -827,6 +746,11 @@ def sync_remote_agents_on_startup():
827
746
  logger = get_logger("cli")
828
747
  logger.warning(f"Failed to deploy agents from cache: {e}")
829
748
 
749
+ # Phase 4: Cleanup legacy agent cache directories (after sync/deployment)
750
+ # CRITICAL: This must run AFTER sync completes because sync may recreate
751
+ # legacy directories. Running cleanup here ensures they're removed.
752
+ cleanup_legacy_agent_cache()
753
+
830
754
  except Exception as e:
831
755
  # Non-critical - log but don't fail startup
832
756
  from ..core.logger import get_logger
@@ -835,8 +759,14 @@ def sync_remote_agents_on_startup():
835
759
  logger.debug(f"Failed to sync remote agents: {e}")
836
760
  # Continue execution - agent sync failure shouldn't block startup
837
761
 
762
+ # Cleanup legacy cache even if sync failed
763
+ try:
764
+ cleanup_legacy_agent_cache()
765
+ except Exception: # nosec B110
766
+ pass # Ignore cleanup errors
767
+
838
768
 
839
- def sync_remote_skills_on_startup():
769
+ def sync_remote_skills_on_startup(force_sync: bool = False):
840
770
  """
841
771
  Synchronize skill templates from remote sources on startup.
842
772
 
@@ -854,6 +784,9 @@ def sync_remote_skills_on_startup():
854
784
  4. Apply profile filtering if active
855
785
  5. Deploy resolved skills to ~/.claude/skills/ - Phase 2 progress bar
856
786
  6. Log deployment results with source indication
787
+
788
+ Args:
789
+ force_sync: Force download even if cache is fresh (bypasses ETag).
857
790
  """
858
791
  try:
859
792
  from pathlib import Path
@@ -959,7 +892,7 @@ def sync_remote_skills_on_startup():
959
892
 
960
893
  # Sync all sources with progress callback
961
894
  results = manager.sync_all_sources(
962
- force=False, progress_callback=sync_progress.update
895
+ force=force_sync, progress_callback=sync_progress.update
963
896
  )
964
897
 
965
898
  # Finish sync progress bar with clear breakdown
@@ -979,150 +912,191 @@ def sync_remote_skills_on_startup():
979
912
 
980
913
  # Phase 2: Scan agents and save to configuration.yaml
981
914
  # This step populates configuration.yaml with agent-referenced skills
982
- if results["synced_count"] > 0:
983
- agents_dir = Path.cwd() / ".claude" / "agents"
984
-
985
- # Scan agents for skill requirements
986
- agent_skills = get_required_skills_from_agents(agents_dir)
987
-
988
- # Save to project-level configuration.yaml
989
- project_config_path = Path.cwd() / ".claude-mpm" / "configuration.yaml"
990
- save_agent_skills_to_config(list(agent_skills), project_config_path)
991
-
992
- # Phase 3: Resolve which skills to deploy (user_defined or agent_referenced)
993
- skills_to_deploy, skill_source = get_skills_to_deploy(project_config_path)
994
-
995
- # Phase 4: Apply profile filtering if active
996
- if active_profile and profile_manager.active_profile:
997
- # Filter skills based on profile
998
- if skills_to_deploy:
999
- # Filter the resolved skill list
1000
- original_count = len(skills_to_deploy)
1001
- filtered_skills = [
1002
- skill
1003
- for skill in skills_to_deploy
1004
- if profile_manager.is_skill_enabled(skill)
1005
- ]
1006
- filtered_count = original_count - len(filtered_skills)
915
+ # CRITICAL: Always scan agents to populate agent_referenced, even when using cached skills.
916
+ # Without this, skill_filter=None causes ALL skills to deploy and NO cleanup to run.
917
+ agents_dir = Path.cwd() / ".claude" / "agents"
918
+
919
+ # Scan agents for skill requirements (ALWAYS run to ensure cleanup works)
920
+ agent_skills = get_required_skills_from_agents(agents_dir)
921
+ logger.info(
922
+ f"Agent scan found {len(agent_skills)} unique skills across deployed agents"
923
+ )
1007
924
 
1008
- # SAFEGUARD: Warn if all skills were filtered out (misconfiguration)
1009
- if not filtered_skills and original_count > 0:
1010
- logger.warning(
1011
- f"Profile '{active_profile}' filtered ALL {original_count} skills. "
1012
- f"This may indicate a naming mismatch in the profile."
1013
- )
1014
- elif filtered_count > 0:
1015
- logger.info(
1016
- f"Profile '{active_profile}' filtered {filtered_count} skills "
1017
- f"({len(filtered_skills)} remaining)"
1018
- )
925
+ # Save to project-level configuration.yaml
926
+ project_config_path = Path.cwd() / ".claude-mpm" / "configuration.yaml"
927
+ save_agent_skills_to_config(list(agent_skills), project_config_path)
928
+ logger.debug(
929
+ f"Saved {len(agent_skills)} agent-referenced skills to {project_config_path}"
930
+ )
1019
931
 
1020
- skills_to_deploy = filtered_skills
1021
- skill_source = f"{skill_source} + profile filtered"
1022
- else:
1023
- # No explicit skill list - filter from all available
1024
- all_skills = manager.get_all_skills()
1025
- filtered_skills = [
1026
- skill["name"]
1027
- for skill in all_skills
1028
- if profile_manager.is_skill_enabled(skill["name"])
1029
- ]
1030
- skills_to_deploy = filtered_skills
1031
- skill_source = "profile filtered"
932
+ # Phase 3: Resolve which skills to deploy (user_defined or agent_referenced)
933
+ skills_to_deploy, skill_source = get_skills_to_deploy(project_config_path)
934
+
935
+ # CRITICAL DEBUG: Log deployment resolution to diagnose cleanup issues
936
+ if skills_to_deploy:
937
+ logger.info(
938
+ f"Resolved {len(skills_to_deploy)} skills from {skill_source} (cleanup will run)"
939
+ )
940
+ else:
941
+ logger.warning(
942
+ f"No skills resolved from {skill_source} - will deploy ALL skills WITHOUT cleanup! "
943
+ f"This may indicate agent_referenced is empty in configuration.yaml."
944
+ )
945
+
946
+ # Phase 4: Apply profile filtering if active
947
+ if active_profile and profile_manager.active_profile:
948
+ # Filter skills based on profile
949
+ if skills_to_deploy:
950
+ # Filter the resolved skill list
951
+ original_count = len(skills_to_deploy)
952
+ filtered_skills = [
953
+ skill
954
+ for skill in skills_to_deploy
955
+ if profile_manager.is_skill_enabled(skill)
956
+ ]
957
+ filtered_count = original_count - len(filtered_skills)
958
+
959
+ # SAFEGUARD: Warn if all skills were filtered out (misconfiguration)
960
+ if not filtered_skills and original_count > 0:
961
+ logger.warning(
962
+ f"Profile '{active_profile}' filtered ALL {original_count} skills. "
963
+ f"This may indicate a naming mismatch in the profile."
964
+ )
965
+ elif filtered_count > 0:
1032
966
  logger.info(
1033
- f"Profile '{active_profile}': "
1034
- f"{len(filtered_skills)} skills enabled from {len(all_skills)} available"
967
+ f"Profile '{active_profile}' filtered {filtered_count} skills "
968
+ f"({len(filtered_skills)} remaining)"
1035
969
  )
1036
970
 
1037
- # Get all skills to determine counts
1038
- all_skills = manager.get_all_skills()
1039
- total_skill_count = len(all_skills)
971
+ skills_to_deploy = filtered_skills
972
+ skill_source = f"{skill_source} + profile filtered"
973
+ else:
974
+ # No explicit skill list - filter from all available
975
+ all_skills = manager.get_all_skills()
976
+ filtered_skills = [
977
+ skill["name"]
978
+ for skill in all_skills
979
+ if profile_manager.is_skill_enabled(skill["name"])
980
+ ]
981
+ skills_to_deploy = filtered_skills
982
+ skill_source = "profile filtered"
983
+ logger.info(
984
+ f"Profile '{active_profile}': "
985
+ f"{len(filtered_skills)} skills enabled from {len(all_skills)} available"
986
+ )
1040
987
 
1041
- # Determine skill count based on resolution
1042
- skill_count = (
1043
- len(skills_to_deploy) if skills_to_deploy else total_skill_count
988
+ # Get all skills to determine counts
989
+ all_skills = manager.get_all_skills()
990
+ total_skill_count = len(all_skills)
991
+
992
+ # Determine skill count based on resolution
993
+ skill_count = len(skills_to_deploy) if skills_to_deploy else total_skill_count
994
+
995
+ if skill_count > 0:
996
+ # Deploy skills with resolved filter
997
+ # Deploy ONLY to project directory (not user-level)
998
+ # DESIGN DECISION: Project-level deployment keeps skills isolated per project,
999
+ # avoiding pollution of user's global ~/.claude/skills/ directory.
1000
+
1001
+ # Deploy to project-local directory with cleanup
1002
+ deployment_result = manager.deploy_skills(
1003
+ target_dir=Path.cwd() / ".claude" / "skills",
1004
+ force=force_sync,
1005
+ # CRITICAL FIX: Empty list should mean "deploy no skills", not "deploy all"
1006
+ # When skills_to_deploy is [], we want skill_filter=set() NOT skill_filter=None
1007
+ # None means "no filtering" (deploy all), empty set means "filter to nothing"
1008
+ skill_filter=set(skills_to_deploy)
1009
+ if skills_to_deploy is not None
1010
+ else None,
1044
1011
  )
1045
1012
 
1046
- if skill_count > 0:
1047
- # Deploy skills with resolved filter
1048
- # Deploy to project directory (like agents), not user directory
1049
- deployment_result = manager.deploy_skills(
1050
- target_dir=Path.cwd() / ".claude" / "skills",
1051
- force=False,
1052
- skill_filter=set(skills_to_deploy) if skills_to_deploy else None,
1013
+ # REMOVED: User-level deployment (lines 1068-1074)
1014
+ # Reason: Skills should be project-specific, not user-global.
1015
+ # Claude Code can read from project-level .claude/skills/ directory.
1016
+
1017
+ # Get actual counts from deployment result (use project-local for display)
1018
+ deployed = deployment_result.get("deployed_count", 0)
1019
+ skipped = deployment_result.get("skipped_count", 0)
1020
+ filtered = deployment_result.get("filtered_count", 0)
1021
+ removed = deployment_result.get("removed_count", 0)
1022
+ total_available = deployed + skipped
1023
+
1024
+ # Only show progress bar if there are skills to deploy
1025
+ if total_available > 0:
1026
+ deploy_progress = ProgressBar(
1027
+ total=total_available,
1028
+ prefix="Deploying skill directories",
1029
+ show_percentage=True,
1030
+ show_counter=True,
1053
1031
  )
1032
+ # Update progress bar to completion
1033
+ deploy_progress.update(total_available)
1034
+ else:
1035
+ # No skills to deploy - create dummy progress for message only
1036
+ deploy_progress = ProgressBar(
1037
+ total=1,
1038
+ prefix="Deploying skill directories",
1039
+ show_percentage=False,
1040
+ show_counter=False,
1041
+ )
1042
+ deploy_progress.update(1)
1054
1043
 
1055
- # Get actual counts from deployment result
1056
- deployed = deployment_result.get("deployed_count", 0)
1057
- skipped = deployment_result.get("skipped_count", 0)
1058
- filtered = deployment_result.get("filtered_count", 0)
1059
- total_available = deployed + skipped
1044
+ # Show total available skills (deployed + already existing)
1045
+ # Include source indication (user_defined vs agent_referenced)
1046
+ # Note: total_skill_count is from cache, total_available is what's deployed/needed
1047
+ source_label = (
1048
+ "user override" if skill_source == "user_defined" else "from agents"
1049
+ )
1060
1050
 
1061
- # Only show progress bar if there are skills to deploy
1062
- if total_available > 0:
1063
- deploy_progress = ProgressBar(
1064
- total=total_available,
1065
- prefix="Deploying skill directories",
1066
- show_percentage=True,
1067
- show_counter=True,
1068
- )
1069
- # Update progress bar to completion
1070
- deploy_progress.update(total_available)
1071
- else:
1072
- # No skills to deploy - create dummy progress for message only
1073
- deploy_progress = ProgressBar(
1074
- total=1,
1075
- prefix="Deploying skill directories",
1076
- show_percentage=False,
1077
- show_counter=False,
1078
- )
1079
- deploy_progress.update(1)
1051
+ # Build finish message with cleanup info
1052
+ if deployed > 0 or removed > 0:
1053
+ parts = []
1054
+ if deployed > 0:
1055
+ parts.append(f"{deployed} new")
1056
+ if skipped > 0:
1057
+ parts.append(f"{skipped} unchanged")
1058
+ if removed > 0:
1059
+ parts.append(f"{removed} removed")
1080
1060
 
1081
- # Show total available skills (deployed + already existing)
1082
- # Include source indication (user_defined vs agent_referenced)
1083
- # Note: total_skill_count is from cache, total_available is what's deployed/needed
1084
- source_label = (
1085
- "user override" if skill_source == "user_defined" else "from agents"
1086
- )
1061
+ status = ", ".join(parts)
1087
1062
 
1088
- if deployed > 0:
1089
- if filtered > 0:
1090
- deploy_progress.finish(
1091
- f"Complete: {deployed} new, {skipped} unchanged "
1092
- f"({total_available} {source_label}, {filtered} files in cache)"
1093
- )
1094
- else:
1095
- deploy_progress.finish(
1096
- f"Complete: {deployed} new, {skipped} unchanged "
1097
- f"({total_available} skills {source_label} from {total_skill_count} files in cache)"
1098
- )
1099
- elif filtered > 0:
1100
- # Skills filtered means agents require fewer skills than available
1063
+ if filtered > 0:
1101
1064
  deploy_progress.finish(
1102
- f"No skills needed ({source_label}, {total_skill_count} files in cache)"
1065
+ f"Complete: {status} ({total_available} {source_label}, {filtered} files in cache)"
1103
1066
  )
1104
1067
  else:
1105
1068
  deploy_progress.finish(
1106
- f"Complete: {total_available} skills {source_label} "
1107
- f"({total_skill_count} files in cache)"
1069
+ f"Complete: {status} ({total_available} skills {source_label} from {total_skill_count} files in cache)"
1108
1070
  )
1071
+ elif filtered > 0:
1072
+ # Skills filtered means agents require fewer skills than available
1073
+ deploy_progress.finish(
1074
+ f"No skills needed ({source_label}, {total_skill_count} files in cache)"
1075
+ )
1076
+ else:
1077
+ # No changes - all skills already deployed
1078
+ msg = f"Complete: {total_available} skills {source_label}"
1079
+ if removed > 0:
1080
+ msg += f", {removed} removed"
1081
+ msg += f" ({total_skill_count} files in cache)"
1082
+ deploy_progress.finish(msg)
1083
+
1084
+ # Log deployment errors if any
1085
+ from ..core.logger import get_logger
1109
1086
 
1110
- # Log deployment errors if any
1111
- from ..core.logger import get_logger
1112
-
1113
- logger = get_logger("cli")
1087
+ logger = get_logger("cli")
1114
1088
 
1115
- errors = deployment_result.get("errors", [])
1116
- if errors:
1117
- logger.warning(
1118
- f"Skill deployment completed with {len(errors)} errors: {errors}"
1119
- )
1089
+ errors = deployment_result.get("errors", [])
1090
+ if errors:
1091
+ logger.warning(
1092
+ f"Skill deployment completed with {len(errors)} errors: {errors}"
1093
+ )
1120
1094
 
1121
- # Log sync errors if any
1122
- if results["failed_count"] > 0:
1123
- logger.warning(
1124
- f"Skill sync completed with {results['failed_count']} failures"
1125
- )
1095
+ # Log sync errors if any
1096
+ if results["failed_count"] > 0:
1097
+ logger.warning(
1098
+ f"Skill sync completed with {results['failed_count']} failures"
1099
+ )
1126
1100
 
1127
1101
  except Exception as e:
1128
1102
  # Non-critical - log but don't fail startup
@@ -1222,61 +1196,64 @@ def show_skill_summary():
1222
1196
  Display skill availability summary on startup.
1223
1197
 
1224
1198
  WHY: Users should see at a glance how many skills are deployed and available
1225
- from collections, similar to the agent summary.
1199
+ from cache, similar to the agent summary showing "X deployed / Y cached".
1226
1200
 
1227
- DESIGN DECISION: Fast, non-blocking check that counts skills from deployment
1228
- directory and collection repos. Shows "X installed (Y available)" format.
1201
+ DESIGN DECISION: Fast, non-blocking check that counts skills from:
1202
+ - Deployed skills: PROJECT-level .claude/skills/ directory
1203
+ - Cached skills: ~/.claude-mpm/cache/skills/ directory (from remote sources)
1204
+
1205
+ Shows format: "✓ Skills: X deployed / Y cached"
1229
1206
  Failures are silent to avoid blocking startup.
1230
1207
  """
1231
1208
  try:
1232
1209
  from pathlib import Path
1233
1210
 
1234
- # Count deployed skills (installed)
1235
- skills_dir = Path.home() / ".claude" / "skills"
1236
- installed_count = 0
1237
- if skills_dir.exists():
1211
+ # Count deployed skills (PROJECT-level, not user-level)
1212
+ project_skills_dir = Path.cwd() / ".claude" / "skills"
1213
+ deployed_count = 0
1214
+ if project_skills_dir.exists():
1238
1215
  # Count directories with SKILL.md (excludes collection repos)
1239
1216
  # Exclude collection directories (obra-superpowers, etc.)
1240
1217
  skill_dirs = [
1241
1218
  d
1242
- for d in skills_dir.iterdir()
1219
+ for d in project_skills_dir.iterdir()
1243
1220
  if d.is_dir()
1244
1221
  and (d / "SKILL.md").exists()
1245
1222
  and not (d / ".git").exists() # Exclude collection repos
1246
1223
  ]
1247
- installed_count = len(skill_dirs)
1224
+ deployed_count = len(skill_dirs)
1248
1225
 
1249
- # Count available skills in collections
1250
- available_count = 0
1251
- if skills_dir.exists():
1252
- # Scan all collection directories (those with .git)
1253
- for collection_dir in skills_dir.iterdir():
1254
- if (
1255
- not collection_dir.is_dir()
1256
- or not (collection_dir / ".git").exists()
1257
- ):
1226
+ # Count cached skills (from remote sources, not deployed yet)
1227
+ # This matches the agent summary pattern: deployed vs cached
1228
+ cache_dir = Path.home() / ".claude-mpm" / "cache" / "skills"
1229
+ cached_count = 0
1230
+ if cache_dir.exists():
1231
+ # Scan all repository directories in cache
1232
+ # Cache structure: ~/.claude-mpm/cache/skills/{owner}/{repo}/...
1233
+ for repo_dir in cache_dir.rglob("*"):
1234
+ if not repo_dir.is_dir():
1258
1235
  continue
1259
1236
 
1260
- # Count skill directories in this collection
1237
+ # Count skill directories (those with SKILL.md)
1261
1238
  # Skills can be nested in: skills/category/skill-name/SKILL.md
1262
1239
  # or in flat structure: skill-name/SKILL.md
1263
- for root, dirs, files in os.walk(collection_dir):
1240
+ for root, dirs, files in os.walk(repo_dir):
1264
1241
  if "SKILL.md" in files:
1265
- # Exclude build artifacts and hidden directories (within the collection)
1266
- # Get relative path from collection_dir to avoid excluding based on .claude parent
1242
+ # Exclude build artifacts and hidden directories
1267
1243
  root_path = Path(root)
1268
- relative_parts = root_path.relative_to(collection_dir).parts
1269
1244
  if not any(
1270
1245
  part.startswith(".")
1271
1246
  or part in ["dist", "build", "__pycache__"]
1272
- for part in relative_parts
1247
+ for part in root_path.parts
1273
1248
  ):
1274
- available_count += 1
1249
+ cached_count += 1
1275
1250
 
1276
- # Display summary if we have skills
1277
- if installed_count > 0 or available_count > 0:
1251
+ # Display summary using agent summary format: "X deployed / Y cached"
1252
+ # Only show non-deployed cached skills (subtract deployed from cached)
1253
+ non_deployed_cached = max(0, cached_count - deployed_count)
1254
+ if deployed_count > 0 or non_deployed_cached > 0:
1278
1255
  print(
1279
- f"✓ Skills: {installed_count} installed ({available_count} available)",
1256
+ f"✓ Skills: {deployed_count} deployed / {non_deployed_cached} cached",
1280
1257
  flush=True,
1281
1258
  )
1282
1259
 
@@ -1289,33 +1266,74 @@ def show_skill_summary():
1289
1266
 
1290
1267
 
1291
1268
  def verify_and_show_pm_skills():
1292
- """Verify PM skills and display status.
1269
+ """Verify PM skills and display status with enhanced validation.
1293
1270
 
1294
- WHY: PM skills are essential for PM agent operation.
1295
- Shows deployment status and auto-deploys if missing.
1271
+ WHY: PM skills are CRITICAL for PM agent operation. PM must KNOW if
1272
+ framework knowledge is unavailable at startup. Enhanced validation
1273
+ checks all required skills exist, are not corrupted, and auto-repairs
1274
+ if needed.
1275
+
1276
+ Shows deployment status:
1277
+ - "✓ PM skills: 8/8 verified" if all required skills are valid
1278
+ - "⚠ PM skills: 2 missing, auto-repairing..." if issues detected
1279
+ - Non-blocking but visible warning if auto-repair fails
1296
1280
  """
1297
1281
  try:
1298
1282
  from pathlib import Path
1299
1283
 
1300
- from ..services.pm_skills_deployer import PMSkillsDeployerService
1284
+ from ..services.pm_skills_deployer import (
1285
+ REQUIRED_PM_SKILLS,
1286
+ PMSkillsDeployerService,
1287
+ )
1301
1288
 
1302
1289
  deployer = PMSkillsDeployerService()
1303
1290
  project_dir = Path.cwd()
1304
1291
 
1305
- result = deployer.verify_pm_skills(project_dir)
1292
+ # Verify with auto-repair enabled
1293
+ result = deployer.verify_pm_skills(project_dir, auto_repair=True)
1306
1294
 
1307
1295
  if result.verified:
1308
- # Show verified status
1309
- print(f"✓ PM skills: {result.skill_count} verified", flush=True)
1296
+ # Show verified status with count
1297
+ total_required = len(REQUIRED_PM_SKILLS)
1298
+ if sys.stdout.isatty():
1299
+ print(
1300
+ f"✓ PM skills: {total_required}/{total_required} verified",
1301
+ flush=True,
1302
+ )
1310
1303
  else:
1311
- # Auto-deploy if missing
1312
- print("Deploying PM skills...", end="", flush=True)
1313
- deploy_result = deployer.deploy_pm_skills(project_dir)
1314
- if deploy_result.success:
1315
- total = len(deploy_result.deployed) + len(deploy_result.skipped)
1316
- print(f"\r✓ PM skills: {total} deployed" + " " * 20, flush=True)
1304
+ # Show warning with details
1305
+ missing_count = len(result.missing_skills)
1306
+ corrupted_count = len(result.corrupted_skills)
1307
+
1308
+ # Build status message
1309
+ issues = []
1310
+ if missing_count > 0:
1311
+ issues.append(f"{missing_count} missing")
1312
+ if corrupted_count > 0:
1313
+ issues.append(f"{corrupted_count} corrupted")
1314
+
1315
+ status = ", ".join(issues)
1316
+
1317
+ # Check if auto-repair was attempted
1318
+ if "Auto-repaired" in result.message:
1319
+ # Auto-repair succeeded
1320
+ total_required = len(REQUIRED_PM_SKILLS)
1321
+ if sys.stdout.isatty():
1322
+ print(
1323
+ f"✓ PM skills: {total_required}/{total_required} verified (auto-repaired)",
1324
+ flush=True,
1325
+ )
1317
1326
  else:
1318
- print(f"\r⚠ PM skills: deployment failed" + " " * 20, flush=True)
1327
+ # Auto-repair failed or not attempted
1328
+ if sys.stdout.isatty():
1329
+ print(f"⚠ PM skills: {status}", flush=True)
1330
+
1331
+ # Log warnings for debugging
1332
+ from ..core.logger import get_logger
1333
+
1334
+ logger = get_logger("cli")
1335
+ for warning in result.warnings:
1336
+ logger.warning(f"PM skills: {warning}")
1319
1337
 
1320
1338
  except ImportError:
1321
1339
  # PM skills deployer not available - skip silently
@@ -1350,7 +1368,7 @@ def auto_install_chrome_devtools_on_startup():
1350
1368
  if not chrome_devtools_config.get("auto_install", True):
1351
1369
  # Auto-install disabled, skip silently
1352
1370
  return
1353
- except Exception:
1371
+ except Exception: # nosec B110
1354
1372
  # If config loading fails, assume auto-install is enabled (default)
1355
1373
  pass
1356
1374
 
@@ -1368,7 +1386,7 @@ def auto_install_chrome_devtools_on_startup():
1368
1386
  # Continue execution - chrome-devtools installation failure shouldn't block startup
1369
1387
 
1370
1388
 
1371
- def run_background_services():
1389
+ def run_background_services(force_sync: bool = False):
1372
1390
  """
1373
1391
  Initialize all background services on startup.
1374
1392
 
@@ -1379,6 +1397,9 @@ def run_background_services():
1379
1397
  explicitly requests them via agent-manager commands. This prevents unwanted
1380
1398
  file creation in project .claude/ directories.
1381
1399
  See: SystemInstructionsDeployer and agent_deployment.py line 504-509
1400
+
1401
+ Args:
1402
+ force_sync: Force download even if cache is fresh (bypasses ETag).
1382
1403
  """
1383
1404
  # Sync hooks early to ensure up-to-date configuration
1384
1405
  # RATIONALE: Hooks should be synced before other services to fix stale configs
@@ -1389,7 +1410,9 @@ def run_background_services():
1389
1410
  check_mcp_auto_configuration()
1390
1411
  verify_mcp_gateway_startup()
1391
1412
  check_for_updates_async()
1392
- sync_remote_agents_on_startup() # Sync agents from remote sources
1413
+ sync_remote_agents_on_startup(
1414
+ force_sync=force_sync
1415
+ ) # Sync agents from remote sources
1393
1416
  show_agent_summary() # Display agent counts after deployment
1394
1417
 
1395
1418
  # Skills deployment order (precedence: remote > bundled)
@@ -1398,7 +1421,9 @@ def run_background_services():
1398
1421
  # 3. Discover and link runtime skills (user-added skills)
1399
1422
  # This ensures remote skills take precedence over bundled skills when names conflict
1400
1423
  deploy_bundled_skills() # Base layer: package-bundled skills
1401
- sync_remote_skills_on_startup() # Override layer: Git-based skills (takes precedence)
1424
+ sync_remote_skills_on_startup(
1425
+ force_sync=force_sync
1426
+ ) # Override layer: Git-based skills (takes precedence)
1402
1427
  discover_and_link_runtime_skills() # Discovery: user-added skills
1403
1428
  show_skill_summary() # Display skill counts after deployment
1404
1429
  verify_and_show_pm_skills() # PM skills verification and status
@@ -1500,7 +1525,9 @@ def check_mcp_auto_configuration():
1500
1525
  from ..services.mcp_gateway.auto_configure import check_and_configure_mcp
1501
1526
 
1502
1527
  # Show progress feedback - this operation can take 10+ seconds
1503
- print("Checking MCP configuration...", end="", flush=True)
1528
+ # Only show progress message in TTY mode to avoid interfering with Claude Code's status display
1529
+ if sys.stdout.isatty():
1530
+ print("Checking MCP configuration...", end="", flush=True)
1504
1531
 
1505
1532
  # This function handles all the logic:
1506
1533
  # - Checks if already configured
@@ -1511,11 +1538,17 @@ def check_mcp_auto_configuration():
1511
1538
  check_and_configure_mcp()
1512
1539
 
1513
1540
  # Clear the "Checking..." message by overwriting with spaces
1514
- print("\r" + " " * 30 + "\r", end="", flush=True)
1541
+ # Only use carriage return clearing if stdout is a real TTY
1542
+ if sys.stdout.isatty():
1543
+ print("\r" + " " * 30 + "\r", end="", flush=True)
1544
+ # In non-TTY mode, don't print anything - the "Checking..." message will just remain on its line
1515
1545
 
1516
1546
  except Exception as e:
1517
1547
  # Clear progress message on error
1518
- print("\r" + " " * 30 + "\r", end="", flush=True)
1548
+ # Only use carriage return clearing if stdout is a real TTY
1549
+ if sys.stdout.isatty():
1550
+ print("\r" + " " * 30 + "\r", end="", flush=True)
1551
+ # In non-TTY mode, don't print anything - the "Checking..." message will just remain on its line
1519
1552
 
1520
1553
  # Non-critical - log but don't fail
1521
1554
  from ..core.logger import get_logger
@@ -1598,7 +1631,7 @@ def verify_mcp_gateway_startup():
1598
1631
  loop.run_until_complete(
1599
1632
  asyncio.gather(*pending, return_exceptions=True)
1600
1633
  )
1601
- except Exception:
1634
+ except Exception: # nosec B110
1602
1635
  pass # Ignore cleanup errors
1603
1636
  finally:
1604
1637
  loop.close()
@@ -1692,7 +1725,7 @@ def check_for_updates_async():
1692
1725
 
1693
1726
  logger = get_logger("upgrade_check")
1694
1727
  logger.debug(f"Update check failed (non-critical): {e}")
1695
- except Exception:
1728
+ except Exception: # nosec B110
1696
1729
  pass # Avoid any errors in error handling
1697
1730
  finally:
1698
1731
  # Properly clean up event loop
@@ -1707,7 +1740,7 @@ def check_for_updates_async():
1707
1740
  loop.run_until_complete(
1708
1741
  asyncio.gather(*pending, return_exceptions=True)
1709
1742
  )
1710
- except Exception:
1743
+ except Exception: # nosec B110
1711
1744
  pass # Ignore cleanup errors
1712
1745
  finally:
1713
1746
  loop.close()