claude-mpm 5.4.65__py3-none-any.whl → 5.4.96__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/CLAUDE_MPM_FOUNDERS_OUTPUT_STYLE.md +405 -0
  3. claude_mpm/agents/CLAUDE_MPM_OUTPUT_STYLE.md +66 -241
  4. claude_mpm/agents/CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md +107 -1928
  5. claude_mpm/agents/PM_INSTRUCTIONS.md +82 -686
  6. claude_mpm/cli/__init__.py +5 -1
  7. claude_mpm/cli/commands/agents.py +2 -4
  8. claude_mpm/cli/commands/agents_reconcile.py +197 -0
  9. claude_mpm/cli/commands/autotodos.py +526 -0
  10. claude_mpm/cli/commands/configure.py +620 -21
  11. claude_mpm/cli/commands/monitor.py +2 -2
  12. claude_mpm/cli/commands/mpm_init/core.py +2 -2
  13. claude_mpm/cli/commands/skills.py +166 -14
  14. claude_mpm/cli/executor.py +89 -0
  15. claude_mpm/cli/interactive/__init__.py +10 -0
  16. claude_mpm/cli/interactive/agent_wizard.py +30 -50
  17. claude_mpm/cli/interactive/questionary_styles.py +65 -0
  18. claude_mpm/cli/interactive/skill_selector.py +481 -0
  19. claude_mpm/cli/parsers/base_parser.py +59 -1
  20. claude_mpm/cli/startup.py +184 -358
  21. claude_mpm/cli/startup_display.py +72 -5
  22. claude_mpm/cli/startup_logging.py +2 -2
  23. claude_mpm/commands/mpm-session-resume.md +1 -1
  24. claude_mpm/constants.py +1 -0
  25. claude_mpm/core/claude_runner.py +2 -2
  26. claude_mpm/core/hook_manager.py +51 -3
  27. claude_mpm/core/interactive_session.py +7 -7
  28. claude_mpm/core/output_style_manager.py +21 -13
  29. claude_mpm/core/unified_config.py +50 -8
  30. claude_mpm/core/unified_paths.py +30 -13
  31. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.C33zOoyM.css +1 -0
  32. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.CW1J-YuA.css +1 -0
  33. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Cs_tUR18.js → 1WZnGYqX.js} +1 -1
  34. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CDuw-vjf.js → 67pF3qNn.js} +1 -1
  35. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{bTOqqlTd.js → 6RxdMKe4.js} +1 -1
  36. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DwBR2MJi.js → 8cZrfX0h.js} +1 -1
  37. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{ZGh7QtNv.js → 9a6T2nm-.js} +1 -1
  38. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{D9lljYKQ.js → B443AUzu.js} +1 -1
  39. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{RJiighC3.js → B8AwtY2H.js} +1 -1
  40. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{uuIeMWc-.js → BF15LAsF.js} +1 -1
  41. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{D3k0OPJN.js → BRcwIQNr.js} +1 -1
  42. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CyWMqx4W.js → BV6nKitt.js} +1 -1
  43. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CiIAseT4.js → BViJ8lZt.js} +5 -5
  44. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CBBdVcY8.js → BcQ-Q0FE.js} +1 -1
  45. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BovzEFCE.js → Bpyvgze_.js} +1 -1
  46. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BzTRqg-z.js +1 -0
  47. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C0Fr8dve.js +1 -0
  48. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{eNVUfhuA.js → C3rbW_a-.js} +1 -1
  49. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{GYwsonyD.js → C8WYN38h.js} +1 -1
  50. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BIF9m_hv.js → C9I8FlXH.js} +1 -1
  51. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B0uc0UOD.js → CIQcWgO2.js} +3 -3
  52. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Be7GpZd6.js → CIctN7YN.js} +1 -1
  53. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Bh0LDWpI.js → CKrS_JZW.js} +2 -2
  54. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DUrLdbGD.js → CR6P9C4A.js} +1 -1
  55. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B7xVLGWV.js → CRRR9MD_.js} +1 -1
  56. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CRcR2DqT.js +334 -0
  57. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Dhb8PKl3.js → CSXtMOf0.js} +1 -1
  58. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BPYeabCQ.js → CT-sbxSk.js} +1 -1
  59. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{sQeU3Y1z.js → CWm6DJsp.js} +1 -1
  60. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CnA0NrzZ.js → CpqQ1Kzn.js} +1 -1
  61. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C4B-KCzX.js → D2nGpDRe.js} +1 -1
  62. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DGkLK5U1.js → D9iCMida.js} +1 -1
  63. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BofRWZRR.js → D9ykgMoY.js} +1 -1
  64. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DmxopI1J.js → DL2Ldur1.js} +1 -1
  65. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C30mlcqg.js → DPfltzjH.js} +1 -1
  66. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Vzk33B_K.js → DR8nis88.js} +2 -2
  67. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DI7hHRFL.js → DUliQN2b.js} +1 -1
  68. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C4JcI4KD.js → DXlhR01x.js} +1 -1
  69. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{bT1r9zLR.js → D_lyTybS.js} +1 -1
  70. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DZX00Y4g.js → DngoTTgh.js} +1 -1
  71. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CzZX-COe.js → DqkmHtDC.js} +1 -1
  72. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B7RN905-.js → DsDh8EYs.js} +1 -1
  73. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DLVjFsZ3.js → DypDmXgd.js} +1 -1
  74. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{iEWssX7S.js → IPYC-LnN.js} +1 -1
  75. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/JTLiF7dt.js +24 -0
  76. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DaimHw_p.js → JpevfAFt.js} +1 -1
  77. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DY1XQ8fi.js → R8CEIRAd.js} +1 -1
  78. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Dle-35c7.js → Zxy7qc-l.js} +2 -2
  79. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/q9Hm6zAU.js +1 -0
  80. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C_Usid8X.js → qtd3IeO4.js} +2 -2
  81. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CzeYkLYB.js → ulBFON_C.js} +2 -2
  82. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Cfqx1Qun.js → wQVh1CoA.js} +1 -1
  83. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/{app.D6-I5TpK.js → app.Dr7t0z2J.js} +2 -2
  84. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.BGhZHUS3.js +1 -0
  85. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/{0.m1gL8KXf.js → 0.RgBboRvH.js} +1 -1
  86. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/{1.CgNOuw-d.js → 1.DG-KkbDf.js} +1 -1
  87. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.D_jnf-x6.js +1 -0
  88. claude_mpm/dashboard/static/svelte-build/_app/version.json +1 -1
  89. claude_mpm/dashboard/static/svelte-build/index.html +9 -9
  90. claude_mpm/hooks/claude_hooks/INTEGRATION_EXAMPLE.md +243 -0
  91. claude_mpm/hooks/claude_hooks/README_AUTO_PAUSE.md +403 -0
  92. claude_mpm/hooks/claude_hooks/__pycache__/auto_pause_handler.cpython-311.pyc +0 -0
  93. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-311.pyc +0 -0
  94. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-311.pyc +0 -0
  95. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-311.pyc +0 -0
  96. claude_mpm/hooks/claude_hooks/auto_pause_handler.py +486 -0
  97. claude_mpm/hooks/claude_hooks/event_handlers.py +216 -11
  98. claude_mpm/hooks/claude_hooks/hook_handler.py +28 -4
  99. claude_mpm/hooks/claude_hooks/response_tracking.py +3 -1
  100. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager.cpython-311.pyc +0 -0
  101. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-311.pyc +0 -0
  102. claude_mpm/hooks/claude_hooks/services/connection_manager.py +20 -0
  103. claude_mpm/hooks/claude_hooks/services/subagent_processor.py +30 -6
  104. claude_mpm/hooks/session_resume_hook.py +85 -1
  105. claude_mpm/init.py +1 -1
  106. claude_mpm/services/agents/cache_git_manager.py +1 -1
  107. claude_mpm/services/agents/deployment/deployment_reconciler.py +577 -0
  108. claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +3 -0
  109. claude_mpm/services/agents/deployment/startup_reconciliation.py +138 -0
  110. claude_mpm/services/agents/startup_sync.py +5 -2
  111. claude_mpm/services/cli/__init__.py +3 -0
  112. claude_mpm/services/cli/incremental_pause_manager.py +561 -0
  113. claude_mpm/services/cli/session_resume_helper.py +10 -2
  114. claude_mpm/services/delegation_detector.py +175 -0
  115. claude_mpm/services/diagnostics/checks/agent_sources_check.py +30 -0
  116. claude_mpm/services/diagnostics/checks/configuration_check.py +24 -0
  117. claude_mpm/services/diagnostics/checks/installation_check.py +22 -0
  118. claude_mpm/services/diagnostics/checks/mcp_services_check.py +23 -0
  119. claude_mpm/services/diagnostics/doctor_reporter.py +31 -1
  120. claude_mpm/services/diagnostics/models.py +14 -1
  121. claude_mpm/services/event_log.py +317 -0
  122. claude_mpm/services/infrastructure/__init__.py +4 -0
  123. claude_mpm/services/infrastructure/context_usage_tracker.py +291 -0
  124. claude_mpm/services/infrastructure/resume_log_generator.py +24 -5
  125. claude_mpm/services/monitor/daemon_manager.py +15 -4
  126. claude_mpm/services/monitor/management/lifecycle.py +8 -2
  127. claude_mpm/services/monitor/server.py +106 -16
  128. claude_mpm/services/pm_skills_deployer.py +177 -83
  129. claude_mpm/services/skills/git_skill_source_manager.py +5 -1
  130. claude_mpm/services/skills/selective_skill_deployer.py +114 -26
  131. claude_mpm/services/socketio/handlers/hook.py +14 -7
  132. claude_mpm/services/socketio/server/main.py +12 -4
  133. claude_mpm/skills/bundled/pm/mpm-agent-update-workflow/SKILL.md +75 -0
  134. claude_mpm/skills/bundled/pm/mpm-bug-reporting/SKILL.md +248 -0
  135. claude_mpm/skills/bundled/pm/mpm-circuit-breaker-enforcement/SKILL.md +476 -0
  136. claude_mpm/skills/bundled/pm/mpm-session-management/SKILL.md +312 -0
  137. claude_mpm/skills/bundled/pm/mpm-teaching-mode/SKILL.md +657 -0
  138. claude_mpm/skills/bundled/pm/mpm-tool-usage-guide/SKILL.md +386 -0
  139. claude_mpm/skills/skill_manager.py +4 -4
  140. claude_mpm/utils/agent_dependency_loader.py +4 -2
  141. claude_mpm/utils/robust_installer.py +10 -6
  142. claude_mpm-5.4.96.dist-info/METADATA +377 -0
  143. {claude_mpm-5.4.65.dist-info → claude_mpm-5.4.96.dist-info}/RECORD +153 -142
  144. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.DWzvg0-y.css +0 -1
  145. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.ThTw9_ym.css +0 -1
  146. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/4TdZjIqw.js +0 -1
  147. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/5shd3_w0.js +0 -24
  148. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BKjSRqUr.js +0 -1
  149. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Da0KfYnO.js +0 -1
  150. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Dfy6j1xT.js +0 -323
  151. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.NWzMBYRp.js +0 -1
  152. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.C0GcWctS.js +0 -1
  153. claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-312.pyc +0 -0
  154. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-312.pyc +0 -0
  155. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-312.pyc +0 -0
  156. claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-312.pyc +0 -0
  157. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-312.pyc +0 -0
  158. claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-312.pyc +0 -0
  159. claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-312.pyc +0 -0
  160. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-312.pyc +0 -0
  161. claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-312.pyc +0 -0
  162. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-312.pyc +0 -0
  163. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-312.pyc +0 -0
  164. claude_mpm-5.4.65.dist-info/METADATA +0 -999
  165. /claude_mpm/skills/bundled/pm/{pm-delegation-patterns → mpm-delegation-patterns}/SKILL.md +0 -0
  166. /claude_mpm/skills/bundled/pm/{pm-git-file-tracking → mpm-git-file-tracking}/SKILL.md +0 -0
  167. /claude_mpm/skills/bundled/pm/{pm-pr-workflow → mpm-pr-workflow}/SKILL.md +0 -0
  168. /claude_mpm/skills/bundled/pm/{pm-ticketing-integration → mpm-ticketing-integration}/SKILL.md +0 -0
  169. /claude_mpm/skills/bundled/pm/{pm-verification-protocols → mpm-verification-protocols}/SKILL.md +0 -0
  170. {claude_mpm-5.4.65.dist-info → claude_mpm-5.4.96.dist-info}/WHEEL +0 -0
  171. {claude_mpm-5.4.65.dist-info → claude_mpm-5.4.96.dist-info}/entry_points.txt +0 -0
  172. {claude_mpm-5.4.65.dist-info → claude_mpm-5.4.96.dist-info}/licenses/LICENSE +0 -0
  173. {claude_mpm-5.4.65.dist-info → claude_mpm-5.4.96.dist-info}/licenses/LICENSE-FAQ.md +0 -0
  174. {claude_mpm-5.4.65.dist-info → claude_mpm-5.4.96.dist-info}/top_level.txt +0 -0
claude_mpm/cli/startup.py CHANGED
@@ -191,7 +191,8 @@ def should_skip_background_services(args, processed_argv):
191
191
  skip_commands = ["--version", "-v", "--help", "-h"]
192
192
  return any(cmd in (processed_argv or sys.argv[1:]) for cmd in skip_commands) or (
193
193
  hasattr(args, "command")
194
- and args.command in ["info", "doctor", "config", "mcp", "configure"]
194
+ and args.command
195
+ in ["info", "doctor", "config", "mcp", "configure", "hook-errors", "autotodos"]
195
196
  )
196
197
 
197
198
 
@@ -234,7 +235,7 @@ def deploy_bundled_skills():
234
235
  if not skills_config.get("auto_deploy", True):
235
236
  # Auto-deploy disabled, skip silently
236
237
  return
237
- except Exception:
238
+ except Exception: # nosec B110
238
239
  # If config loading fails, assume auto-deploy is enabled (default)
239
240
  pass
240
241
 
@@ -308,68 +309,60 @@ def deploy_output_style_on_startup():
308
309
  communication without emojis and exclamation points. Styles are project-specific
309
310
  to allow different projects to have different communication styles.
310
311
 
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.
312
+ DESIGN DECISION: This is non-blocking and idempotent. Deploys to user-level
313
+ directory (~/.claude/output-styles/) which is the official Claude Code location
314
+ for custom output styles.
314
315
 
315
- Deploys two styles:
316
- - claude-mpm-style.md (professional mode)
317
- - claude-mpm-teach.md (teaching mode)
316
+ Deploys all styles:
317
+ - claude-mpm.md (professional mode)
318
+ - claude-mpm-teacher.md (teaching mode)
319
+ - claude-mpm-founders.md (founders mode)
318
320
  """
319
321
  try:
320
- import shutil
321
- from pathlib import Path
322
+ from ..core.output_style_manager import OutputStyleManager
322
323
 
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"
327
-
328
- # Target directory (USER-LEVEL for global availability)
329
- # Claude Code reads output styles from ~/.claude/settings/output-styles/
330
- user_home = Path.home()
331
- output_styles_dir = user_home / ".claude" / "settings" / "output-styles"
332
- professional_target = output_styles_dir / "claude-mpm-style.md"
333
- teacher_target = output_styles_dir / "claude-mpm-teach.md"
334
-
335
- # Create directory if it doesn't exist
336
- output_styles_dir.mkdir(parents=True, exist_ok=True)
337
-
338
- # Check if already deployed AND up-to-date (compare sizes to detect changes)
339
- professional_up_to_date = (
340
- professional_target.exists()
341
- and professional_source.exists()
342
- and professional_target.stat().st_size == professional_source.stat().st_size
343
- )
344
- teacher_up_to_date = (
345
- teacher_target.exists()
346
- and teacher_source.exists()
347
- and teacher_target.stat().st_size == teacher_source.stat().st_size
348
- )
324
+ # Initialize the output style manager
325
+ manager = OutputStyleManager()
349
326
 
350
- if professional_up_to_date and teacher_up_to_date:
327
+ # Check if Claude Code version supports output styles (>= 1.0.83)
328
+ if not manager.supports_output_styles():
329
+ # Skip deployment for older versions
330
+ # The manager will fall back to injecting content directly
331
+ return
332
+
333
+ # Check if all styles are already deployed and up-to-date
334
+ all_up_to_date = True
335
+ for style_config in manager.styles.values():
336
+ source_path = style_config["source"]
337
+ target_path = style_config["target"]
338
+
339
+ if not (
340
+ target_path.exists()
341
+ and source_path.exists()
342
+ and target_path.stat().st_size == source_path.stat().st_size
343
+ ):
344
+ all_up_to_date = False
345
+ break
346
+
347
+ if all_up_to_date:
351
348
  # Show feedback that output styles are ready
352
349
  print("✓ Output styles ready", flush=True)
353
350
  return
354
351
 
355
- # Deploy both styles
356
- deployed_count = 0
357
- if professional_source.exists():
358
- shutil.copy2(professional_source, professional_target)
359
- deployed_count += 1
352
+ # Deploy all styles using the manager
353
+ results = manager.deploy_all_styles(activate_default=True)
360
354
 
361
- if teacher_source.exists():
362
- shutil.copy2(teacher_source, teacher_target)
363
- deployed_count += 1
355
+ # Count successful deployments
356
+ deployed_count = sum(1 for success in results.values() if success)
364
357
 
365
358
  if deployed_count > 0:
366
359
  print(f"✓ Output styles deployed ({deployed_count} styles)", flush=True)
367
360
  else:
368
- # Source files missing - log but don't fail
361
+ # Deployment failed - log but don't fail startup
369
362
  from ..core.logger import get_logger
370
363
 
371
364
  logger = get_logger("cli")
372
- logger.debug("Output style source files not found")
365
+ logger.debug("Failed to deploy any output styles")
373
366
 
374
367
  except Exception as e:
375
368
  # Non-critical - log but don't fail startup
@@ -458,7 +451,7 @@ def _cleanup_orphaned_agents(deploy_target: Path, deployed_agents: list[str]) ->
458
451
  return removed_count
459
452
 
460
453
 
461
- def sync_remote_agents_on_startup():
454
+ def sync_remote_agents_on_startup(force_sync: bool = False):
462
455
  """
463
456
  Synchronize agent templates from remote sources on startup.
464
457
 
@@ -476,6 +469,9 @@ def sync_remote_agents_on_startup():
476
469
  3. Cleanup orphaned agents (ours but no longer deployed) - Phase 3
477
470
  4. Cleanup legacy agent cache directories (after sync/deployment) - Phase 4
478
471
  5. Log deployment results
472
+
473
+ Args:
474
+ force_sync: Force download even if cache is fresh (bypasses ETag).
479
475
  """
480
476
  # DEPRECATED: Legacy warning - no-op function, kept for compatibility
481
477
  check_legacy_cache()
@@ -486,7 +482,6 @@ def sync_remote_agents_on_startup():
486
482
  from pathlib import Path
487
483
 
488
484
  from ..core.shared.config_loader import ConfigLoader
489
- from ..services.agents.deployment.agent_deployment import AgentDeploymentService
490
485
  from ..services.agents.startup_sync import sync_agents_on_startup
491
486
  from ..services.profile_manager import ProfileManager
492
487
  from ..utils.progress import ProgressBar
@@ -511,7 +506,7 @@ def sync_remote_agents_on_startup():
511
506
  )
512
507
 
513
508
  # Phase 1: Sync files from Git sources
514
- result = sync_agents_on_startup()
509
+ result = sync_agents_on_startup(force_refresh=force_sync)
515
510
 
516
511
  # Only proceed with deployment if sync was enabled and ran
517
512
  if result.get("enabled") and result.get("sources_synced", 0) > 0:
@@ -534,305 +529,89 @@ def sync_remote_agents_on_startup():
534
529
  logger.warning(f"Agent sync completed with {len(errors)} errors")
535
530
 
536
531
  # Phase 2: Deploy agents from cache to ~/.claude/agents/
537
- # This mirrors the skills deployment pattern (lines 371-407)
532
+ # Use reconciliation service to respect configuration.yaml settings
538
533
  try:
539
- # Initialize deployment service with profile-filtered configuration
540
- from ..core.config import Config
541
-
542
- deploy_config = None
543
- if active_profile and profile_manager.active_profile:
544
- # Create config with excluded agents based on profile
545
- # Get all agents that should be excluded (not in enabled list)
546
- from pathlib import Path
547
-
548
- cache_dir = Path.home() / ".claude-mpm" / "cache" / "agents"
549
- if cache_dir.exists():
550
- # Find all agent files
551
- # Supports both flat cache and {owner}/{repo}/agents/ structure
552
- all_agent_files = [
553
- f
554
- for f in cache_dir.rglob("*.md")
555
- if "/agents/" in str(f)
556
- and f.stem.lower() != "base-agent"
557
- and f.name.lower()
558
- not in {"readme.md", "changelog.md", "contributing.md"}
559
- ]
560
-
561
- # Build exclusion list for agents not in profile
562
- excluded_agents = []
563
- for agent_file in all_agent_files:
564
- agent_name = agent_file.stem
565
- if not profile_manager.is_agent_enabled(agent_name):
566
- excluded_agents.append(agent_name)
567
-
568
- if excluded_agents:
569
- # Get singleton config and update with profile settings
570
- # BUGFIX: Config is a singleton that ignores dict parameter if already initialized.
571
- # Creating Config({...}) doesn't store excluded_agents - use set() instead.
572
- deploy_config = Config()
573
- deploy_config.set(
574
- "agent_deployment.excluded_agents", excluded_agents
575
- )
576
- deploy_config.set(
577
- "agent_deployment.filter_non_mpm_agents", False
578
- )
579
- deploy_config.set("agent_deployment.case_sensitive", False)
580
- deploy_config.set(
581
- "agent_deployment.exclude_dependencies", False
582
- )
583
- logger.info(
584
- f"Profile '{active_profile}': Excluding {len(excluded_agents)} agents from deployment"
585
- )
586
-
587
- deployment_service = AgentDeploymentService(config=deploy_config)
588
-
589
- # Count agents in cache to show accurate progress
590
534
  from pathlib import Path
591
535
 
592
- cache_dir = Path.home() / ".claude-mpm" / "cache" / "agents"
593
- agent_count = 0
594
-
595
- if cache_dir.exists():
596
- # BUGFIX (cache-count-inflation): Clean up stale cache files
597
- # from old repositories before counting to prevent inflated counts.
598
- # Issue: Old caches like bobmatnyc/claude-mpm-agents/agents/
599
- # were counted alongside current agents, inflating count
600
- # from 44 to 85.
601
- #
602
- # Solution: Remove files with nested /agents/ paths
603
- # (e.g., cache/agents/user/repo/agents/...)
604
- # Keep only current agents (e.g., cache/agents/engineer/...)
605
- removed_count = 0
606
- stale_dirs = set()
607
-
608
- for md_file in cache_dir.rglob("*.md"):
609
- # Stale cache files have multiple /agents/ in their path RELATIVE to cache_dir
610
- # Current: cache/agents/bobmatnyc/claude-mpm-agents/agents/engineer/...
611
- # (1 occurrence in relative path: /agents/)
612
- # Old flat: cache/agents/engineer/...
613
- # (0 occurrences in relative path - no repo structure)
614
- # The issue: str(md_file).count("/agents/") counts BOTH cache/agents/ AND repo/agents/
615
- # Fix: Count /agents/ in path RELATIVE to cache_dir (after cache/agents/)
616
- relative_path = str(md_file.relative_to(cache_dir))
617
- if relative_path.count("/agents/") > 1:
618
- # Track parent directory for cleanup
619
- # Extract subdirectory under cache/agents/
620
- # (e.g., "bobmatnyc")
621
- parts = md_file.parts
622
- cache_agents_idx = parts.index("agents")
623
- if cache_agents_idx + 1 < len(parts):
624
- stale_subdir = parts[cache_agents_idx + 1]
625
- # Only remove if it's not a known category directory
626
- if stale_subdir not in [
627
- "engineer",
628
- "ops",
629
- "qa",
630
- "universal",
631
- "documentation",
632
- "claude-mpm",
633
- "security",
634
- ]:
635
- stale_dirs.add(cache_dir / stale_subdir)
636
-
637
- md_file.unlink()
638
- removed_count += 1
639
-
640
- # Remove empty stale directories
641
- for stale_dir in stale_dirs:
642
- if stale_dir.exists() and stale_dir.is_dir():
643
- try:
644
- # Remove directory and all contents
645
- import shutil
646
-
647
- shutil.rmtree(stale_dir)
648
- except Exception:
649
- pass # Ignore cleanup errors
650
-
651
- if removed_count > 0:
652
- from loguru import logger
653
-
654
- logger.info(
655
- f"Cleaned up {removed_count} stale cache files "
656
- f"from old repositories"
657
- )
536
+ from ..core.unified_config import UnifiedConfig
537
+ from ..services.agents.deployment.startup_reconciliation import (
538
+ perform_startup_reconciliation,
539
+ )
658
540
 
659
- # Count MD files in cache (agent markdown files from
660
- # current repos)
661
- # BUGFIX: Only count files in agent directories,
662
- # not docs/templates/READMEs
663
- # Valid agent paths must contain "/agents/" exactly ONCE
664
- # (current structure)
665
- # Exclude PM templates, BASE-AGENT, and documentation files
666
- pm_templates = {
667
- "base-agent.md",
668
- "circuit_breakers.md",
669
- "pm_examples.md",
670
- "pm_red_flags.md",
671
- "research_gate_examples.md",
672
- "response_format.md",
673
- "ticket_completeness_examples.md",
674
- "validation_templates.md",
675
- "git_file_tracking.md",
676
- }
677
- # Documentation files to exclude (by filename)
678
- doc_files = {
679
- "readme.md",
680
- "changelog.md",
681
- "contributing.md",
682
- "implementation-summary.md",
683
- "reorganization-plan.md",
684
- "auto-deploy-index.md",
685
- }
686
-
687
- # Find all markdown files (after cleanup)
688
- all_md_files = list(cache_dir.rglob("*.md"))
689
-
690
- # Filter to only agent files:
691
- # 1. Must have "/agents/" in path (current structure supports
692
- # both flat and {owner}/{repo}/agents/ patterns)
693
- # 2. Must not be in PM templates or doc files
694
- # 3. Exclude BASE-AGENT.md which is not a deployable agent
695
- # 4. Exclude build artifacts (dist/, build/, .cache/)
696
- # to prevent double-counting
697
- agent_files = [
698
- f
699
- for f in all_md_files
700
- if
701
- (
702
- # Must be in an agent directory
703
- # Supports: cache/agents/{category}/... (flat)
704
- # Supports: cache/agents/{owner}/{repo}/agents/{category}/... (GitHub sync)
705
- "/agents/" in str(f)
706
- # Exclude PM templates, doc files, and BASE-AGENT
707
- and f.name.lower() not in pm_templates
708
- and f.name.lower() not in doc_files
709
- and f.name.lower() != "base-agent.md"
710
- # Exclude build artifacts (prevents double-counting
711
- # source + built files)
712
- and not any(
713
- part in str(f).split("/")
714
- for part in ["dist", "build", ".cache"]
715
- )
716
- )
717
- ]
718
- agent_count = len(agent_files)
719
-
720
- if agent_count > 0:
721
- # Deploy agents to project-level directory where Claude Code expects them
722
- deploy_target = Path.cwd() / ".claude" / "agents"
723
- deployment_result = deployment_service.deploy_agents(
724
- target_dir=deploy_target,
725
- force_rebuild=False, # Only deploy if versions differ
726
- deployment_mode="update", # Version-aware updates
727
- config=deploy_config, # Pass config to respect profile filtering
541
+ # Load configuration
542
+ unified_config = UnifiedConfig()
543
+
544
+ # Override with profile settings if active
545
+ if active_profile and profile_manager.active_profile:
546
+ # Get enabled agents from profile (returns Set[str])
547
+ profile_enabled_agents = (
548
+ profile_manager.active_profile.get_enabled_agents()
549
+ )
550
+ # Update config with profile's enabled list (convert Set to List)
551
+ unified_config.agents.enabled = list(profile_enabled_agents)
552
+ logger.info(
553
+ f"Profile '{active_profile}': Using {len(profile_enabled_agents)} enabled agents"
728
554
  )
729
555
 
730
- # Get actual counts from deployment result (reflects configured agents)
731
- deployed = len(deployment_result.get("deployed", []))
732
- updated = len(deployment_result.get("updated", []))
733
- skipped = len(deployment_result.get("skipped", []))
734
- total_configured = deployed + updated + skipped
735
-
736
- # FALLBACK: If deployment result doesn't track skipped agents (async path),
737
- # count existing agents in target directory as "already deployed"
738
- # This ensures accurate reporting when agents are already up-to-date
739
- if total_configured == 0 and deploy_target.exists():
740
- existing_agents = list(deploy_target.glob("*.md"))
741
- # Filter out non-agent files (e.g., README.md, INSTRUCTIONS.md)
742
- agent_count_in_target = len(
743
- [
744
- f
745
- for f in existing_agents
746
- if not f.name.startswith(("README", "INSTRUCTIONS"))
747
- ]
748
- )
749
- if agent_count_in_target > 0:
750
- # All agents already deployed - count them as skipped
751
- skipped = agent_count_in_target
752
- total_configured = agent_count_in_target
556
+ # Perform reconciliation to deploy configured agents
557
+ project_path = Path.cwd()
558
+ agent_result, _skill_result = perform_startup_reconciliation(
559
+ project_path=project_path, config=unified_config, silent=False
560
+ )
561
+
562
+ # Display results with progress bar
563
+ total_operations = (
564
+ len(agent_result.deployed)
565
+ + len(agent_result.removed)
566
+ + len(agent_result.unchanged)
567
+ )
753
568
 
754
- # Create progress bar with actual configured agent count (not raw file count)
569
+ if total_operations > 0:
755
570
  deploy_progress = ProgressBar(
756
- total=total_configured if total_configured > 0 else 1,
571
+ total=total_operations,
757
572
  prefix="Deploying agents",
758
573
  show_percentage=True,
759
574
  show_counter=True,
760
575
  )
761
-
762
- # Update progress bar to completion
763
- deploy_progress.update(
764
- total_configured if total_configured > 0 else 1
765
- )
766
-
767
- # Cleanup orphaned agents (ours but no longer deployed)
768
- # Get list of deployed agent filenames (what should remain)
769
- deployed_filenames = []
770
- for agent_name in deployment_result.get("deployed", []):
771
- deployed_filenames.append(f"{agent_name}.md")
772
- for agent_name in deployment_result.get("updated", []):
773
- deployed_filenames.append(f"{agent_name}.md")
774
- for agent_name in deployment_result.get("skipped", []):
775
- deployed_filenames.append(f"{agent_name}.md")
776
-
777
- # Run cleanup and get count of removed agents
778
- removed = _cleanup_orphaned_agents(
779
- deploy_target, deployed_filenames
576
+ deploy_progress.update(total_operations)
577
+
578
+ # Build summary message
579
+ deployed = len(agent_result.deployed)
580
+ removed = len(agent_result.removed)
581
+ unchanged = len(agent_result.unchanged)
582
+
583
+ summary_parts = []
584
+ if deployed > 0:
585
+ summary_parts.append(f"{deployed} new")
586
+ if removed > 0:
587
+ summary_parts.append(f"{removed} removed")
588
+ if unchanged > 0:
589
+ summary_parts.append(f"{unchanged} unchanged")
590
+
591
+ summary = f"Complete: {', '.join(summary_parts)}"
592
+ deploy_progress.finish(summary)
593
+
594
+ # Display errors if any
595
+ if agent_result.errors:
596
+ logger.warning(
597
+ f"Agent deployment completed with {len(agent_result.errors)} errors"
780
598
  )
599
+ print("\n⚠️ Agent Deployment Errors:")
600
+ max_errors_to_show = 10
601
+ errors_to_display = agent_result.errors[:max_errors_to_show]
781
602
 
782
- # Show total configured agents (deployed + updated + already existing)
783
- # Include cache count for context and removed count if any
784
- if deployed > 0 or updated > 0:
785
- if removed > 0:
786
- deploy_progress.finish(
787
- f"Complete: {deployed} new, {updated} updated, {skipped} unchanged, "
788
- f"{removed} removed ({total_configured} configured from {agent_count} files in cache)"
789
- )
790
- else:
791
- deploy_progress.finish(
792
- f"Complete: {deployed} new, {updated} updated, {skipped} unchanged "
793
- f"({total_configured} configured from {agent_count} files in cache)"
794
- )
795
- elif removed > 0:
796
- deploy_progress.finish(
797
- f"Complete: {total_configured} agents deployed, "
798
- f"{removed} removed ({agent_count} files in cache)"
799
- )
800
- else:
801
- deploy_progress.finish(
802
- f"Complete: {total_configured} agents deployed "
803
- f"({agent_count} files in cache)"
804
- )
805
-
806
- # Display deployment errors to user (not just logs)
807
- deploy_errors = deployment_result.get("errors", [])
808
- if deploy_errors:
809
- # Log for debugging
810
- logger.warning(
811
- f"Agent deployment completed with {len(deploy_errors)} errors: {deploy_errors}"
812
- )
603
+ for error in errors_to_display:
604
+ print(f" - {error}")
813
605
 
814
- # Display errors to user with clear formatting
815
- print("\n⚠️ Agent Deployment Errors:")
606
+ if len(agent_result.errors) > max_errors_to_show:
607
+ remaining = len(agent_result.errors) - max_errors_to_show
608
+ print(f" ... and {remaining} more error(s)")
816
609
 
817
- # Show first 10 errors to avoid overwhelming output
818
- max_errors_to_show = 10
819
- errors_to_display = deploy_errors[:max_errors_to_show]
820
-
821
- for error in errors_to_display:
822
- # Format error message for readability
823
- # Errors typically come as strings like "agent.md: Error message"
824
- print(f" - {error}")
825
-
826
- # If more errors exist, show count
827
- if len(deploy_errors) > max_errors_to_show:
828
- remaining = len(deploy_errors) - max_errors_to_show
829
- print(f" ... and {remaining} more error(s)")
830
-
831
- # Show summary message
832
- print(
833
- f"\n❌ Failed to deploy {len(deploy_errors)} agent(s). Please check the error messages above."
834
- )
835
- print(" Run with --verbose for detailed error information.\n")
610
+ print(
611
+ f"\n❌ Failed to deploy {len(agent_result.errors)} agent(s). "
612
+ "Please check the error messages above."
613
+ )
614
+ print(" Run with --verbose for detailed error information.\n")
836
615
 
837
616
  except Exception as e:
838
617
  # Deployment failure shouldn't block startup
@@ -857,11 +636,11 @@ def sync_remote_agents_on_startup():
857
636
  # Cleanup legacy cache even if sync failed
858
637
  try:
859
638
  cleanup_legacy_agent_cache()
860
- except Exception:
639
+ except Exception: # nosec B110
861
640
  pass # Ignore cleanup errors
862
641
 
863
642
 
864
- def sync_remote_skills_on_startup():
643
+ def sync_remote_skills_on_startup(force_sync: bool = False):
865
644
  """
866
645
  Synchronize skill templates from remote sources on startup.
867
646
 
@@ -879,6 +658,9 @@ def sync_remote_skills_on_startup():
879
658
  4. Apply profile filtering if active
880
659
  5. Deploy resolved skills to ~/.claude/skills/ - Phase 2 progress bar
881
660
  6. Log deployment results with source indication
661
+
662
+ Args:
663
+ force_sync: Force download even if cache is fresh (bypasses ETag).
882
664
  """
883
665
  try:
884
666
  from pathlib import Path
@@ -984,7 +766,7 @@ def sync_remote_skills_on_startup():
984
766
 
985
767
  # Sync all sources with progress callback
986
768
  results = manager.sync_all_sources(
987
- force=False, progress_callback=sync_progress.update
769
+ force=force_sync, progress_callback=sync_progress.update
988
770
  )
989
771
 
990
772
  # Finish sync progress bar with clear breakdown
@@ -1093,7 +875,7 @@ def sync_remote_skills_on_startup():
1093
875
  # Deploy to project-local directory with cleanup
1094
876
  deployment_result = manager.deploy_skills(
1095
877
  target_dir=Path.cwd() / ".claude" / "skills",
1096
- force=False,
878
+ force=force_sync,
1097
879
  # CRITICAL FIX: Empty list should mean "deploy no skills", not "deploy all"
1098
880
  # When skills_to_deploy is [], we want skill_filter=set() NOT skill_filter=None
1099
881
  # None means "no filtering" (deploy all), empty set means "filter to nothing"
@@ -1358,33 +1140,70 @@ def show_skill_summary():
1358
1140
 
1359
1141
 
1360
1142
  def verify_and_show_pm_skills():
1361
- """Verify PM skills and display status.
1143
+ """Verify PM skills and display status with enhanced validation.
1144
+
1145
+ WHY: PM skills are CRITICAL for PM agent operation. PM must KNOW if
1146
+ framework knowledge is unavailable at startup. Enhanced validation
1147
+ checks all required skills exist, are not corrupted, and auto-repairs
1148
+ if needed.
1362
1149
 
1363
- WHY: PM skills are essential for PM agent operation.
1364
- Shows deployment status and auto-deploys if missing.
1150
+ Shows deployment status:
1151
+ - "✓ PM skills: 8/8 verified" if all required skills are valid
1152
+ - "⚠ PM skills: 2 missing, auto-repairing..." if issues detected
1153
+ - Non-blocking but visible warning if auto-repair fails
1365
1154
  """
1366
1155
  try:
1367
1156
  from pathlib import Path
1368
1157
 
1369
- from ..services.pm_skills_deployer import PMSkillsDeployerService
1158
+ from ..services.pm_skills_deployer import (
1159
+ REQUIRED_PM_SKILLS,
1160
+ PMSkillsDeployerService,
1161
+ )
1370
1162
 
1371
1163
  deployer = PMSkillsDeployerService()
1372
1164
  project_dir = Path.cwd()
1373
1165
 
1374
- result = deployer.verify_pm_skills(project_dir)
1166
+ # Verify with auto-repair enabled
1167
+ result = deployer.verify_pm_skills(project_dir, auto_repair=True)
1375
1168
 
1376
1169
  if result.verified:
1377
- # Show verified status
1378
- print(f"✓ PM skills: {result.skill_count} verified", flush=True)
1170
+ # Show verified status with count
1171
+ total_required = len(REQUIRED_PM_SKILLS)
1172
+ print(
1173
+ f"✓ PM skills: {total_required}/{total_required} verified", flush=True
1174
+ )
1379
1175
  else:
1380
- # Auto-deploy if missing
1381
- print("Deploying PM skills...", end="", flush=True)
1382
- deploy_result = deployer.deploy_pm_skills(project_dir)
1383
- if deploy_result.success:
1384
- total = len(deploy_result.deployed) + len(deploy_result.skipped)
1385
- print(f"\r✓ PM skills: {total} deployed" + " " * 20, flush=True)
1176
+ # Show warning with details
1177
+ missing_count = len(result.missing_skills)
1178
+ corrupted_count = len(result.corrupted_skills)
1179
+
1180
+ # Build status message
1181
+ issues = []
1182
+ if missing_count > 0:
1183
+ issues.append(f"{missing_count} missing")
1184
+ if corrupted_count > 0:
1185
+ issues.append(f"{corrupted_count} corrupted")
1186
+
1187
+ status = ", ".join(issues)
1188
+
1189
+ # Check if auto-repair was attempted
1190
+ if "Auto-repaired" in result.message:
1191
+ # Auto-repair succeeded
1192
+ total_required = len(REQUIRED_PM_SKILLS)
1193
+ print(
1194
+ f"✓ PM skills: {total_required}/{total_required} verified (auto-repaired)",
1195
+ flush=True,
1196
+ )
1386
1197
  else:
1387
- print("\r⚠ PM skills: deployment failed" + " " * 20, flush=True)
1198
+ # Auto-repair failed or not attempted
1199
+ print(f"⚠ PM skills: {status}", flush=True)
1200
+
1201
+ # Log warnings for debugging
1202
+ from ..core.logger import get_logger
1203
+
1204
+ logger = get_logger("cli")
1205
+ for warning in result.warnings:
1206
+ logger.warning(f"PM skills: {warning}")
1388
1207
 
1389
1208
  except ImportError:
1390
1209
  # PM skills deployer not available - skip silently
@@ -1419,7 +1238,7 @@ def auto_install_chrome_devtools_on_startup():
1419
1238
  if not chrome_devtools_config.get("auto_install", True):
1420
1239
  # Auto-install disabled, skip silently
1421
1240
  return
1422
- except Exception:
1241
+ except Exception: # nosec B110
1423
1242
  # If config loading fails, assume auto-install is enabled (default)
1424
1243
  pass
1425
1244
 
@@ -1437,7 +1256,7 @@ def auto_install_chrome_devtools_on_startup():
1437
1256
  # Continue execution - chrome-devtools installation failure shouldn't block startup
1438
1257
 
1439
1258
 
1440
- def run_background_services():
1259
+ def run_background_services(force_sync: bool = False):
1441
1260
  """
1442
1261
  Initialize all background services on startup.
1443
1262
 
@@ -1448,6 +1267,9 @@ def run_background_services():
1448
1267
  explicitly requests them via agent-manager commands. This prevents unwanted
1449
1268
  file creation in project .claude/ directories.
1450
1269
  See: SystemInstructionsDeployer and agent_deployment.py line 504-509
1270
+
1271
+ Args:
1272
+ force_sync: Force download even if cache is fresh (bypasses ETag).
1451
1273
  """
1452
1274
  # Sync hooks early to ensure up-to-date configuration
1453
1275
  # RATIONALE: Hooks should be synced before other services to fix stale configs
@@ -1458,7 +1280,9 @@ def run_background_services():
1458
1280
  check_mcp_auto_configuration()
1459
1281
  verify_mcp_gateway_startup()
1460
1282
  check_for_updates_async()
1461
- sync_remote_agents_on_startup() # Sync agents from remote sources
1283
+ sync_remote_agents_on_startup(
1284
+ force_sync=force_sync
1285
+ ) # Sync agents from remote sources
1462
1286
  show_agent_summary() # Display agent counts after deployment
1463
1287
 
1464
1288
  # Skills deployment order (precedence: remote > bundled)
@@ -1467,7 +1291,9 @@ def run_background_services():
1467
1291
  # 3. Discover and link runtime skills (user-added skills)
1468
1292
  # This ensures remote skills take precedence over bundled skills when names conflict
1469
1293
  deploy_bundled_skills() # Base layer: package-bundled skills
1470
- sync_remote_skills_on_startup() # Override layer: Git-based skills (takes precedence)
1294
+ sync_remote_skills_on_startup(
1295
+ force_sync=force_sync
1296
+ ) # Override layer: Git-based skills (takes precedence)
1471
1297
  discover_and_link_runtime_skills() # Discovery: user-added skills
1472
1298
  show_skill_summary() # Display skill counts after deployment
1473
1299
  verify_and_show_pm_skills() # PM skills verification and status
@@ -1667,7 +1493,7 @@ def verify_mcp_gateway_startup():
1667
1493
  loop.run_until_complete(
1668
1494
  asyncio.gather(*pending, return_exceptions=True)
1669
1495
  )
1670
- except Exception:
1496
+ except Exception: # nosec B110
1671
1497
  pass # Ignore cleanup errors
1672
1498
  finally:
1673
1499
  loop.close()
@@ -1761,7 +1587,7 @@ def check_for_updates_async():
1761
1587
 
1762
1588
  logger = get_logger("upgrade_check")
1763
1589
  logger.debug(f"Update check failed (non-critical): {e}")
1764
- except Exception:
1590
+ except Exception: # nosec B110
1765
1591
  pass # Avoid any errors in error handling
1766
1592
  finally:
1767
1593
  # Properly clean up event loop
@@ -1776,7 +1602,7 @@ def check_for_updates_async():
1776
1602
  loop.run_until_complete(
1777
1603
  asyncio.gather(*pending, return_exceptions=True)
1778
1604
  )
1779
- except Exception:
1605
+ except Exception: # nosec B110
1780
1606
  pass # Ignore cleanup errors
1781
1607
  finally:
1782
1608
  loop.close()