claude-mpm 5.4.21__py3-none-any.whl → 5.4.59__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 (176) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/BASE_AGENT.md +164 -0
  3. claude_mpm/agents/BASE_ENGINEER.md +658 -0
  4. claude_mpm/agents/MEMORY.md +1 -1
  5. claude_mpm/agents/PM_INSTRUCTIONS.md +771 -1019
  6. claude_mpm/agents/WORKFLOW.md +5 -254
  7. claude_mpm/agents/agent_loader.py +1 -1
  8. claude_mpm/agents/base_agent.json +31 -0
  9. claude_mpm/agents/frontmatter_validator.py +2 -2
  10. claude_mpm/cli/commands/agent_state_manager.py +10 -10
  11. claude_mpm/cli/commands/agents.py +9 -9
  12. claude_mpm/cli/commands/auto_configure.py +4 -4
  13. claude_mpm/cli/commands/configure.py +1 -1
  14. claude_mpm/cli/commands/configure_agent_display.py +12 -0
  15. claude_mpm/cli/commands/mpm_init/core.py +72 -0
  16. claude_mpm/cli/commands/postmortem.py +1 -1
  17. claude_mpm/cli/commands/profile.py +276 -0
  18. claude_mpm/cli/commands/skills.py +14 -18
  19. claude_mpm/cli/executor.py +10 -0
  20. claude_mpm/cli/interactive/agent_wizard.py +2 -2
  21. claude_mpm/cli/parsers/base_parser.py +7 -0
  22. claude_mpm/cli/parsers/profile_parser.py +147 -0
  23. claude_mpm/cli/parsers/skills_parser.py +0 -6
  24. claude_mpm/cli/startup.py +506 -180
  25. claude_mpm/commands/mpm-config.md +13 -250
  26. claude_mpm/commands/mpm-doctor.md +9 -22
  27. claude_mpm/commands/mpm-help.md +5 -206
  28. claude_mpm/commands/mpm-init.md +81 -507
  29. claude_mpm/commands/mpm-monitor.md +15 -402
  30. claude_mpm/commands/mpm-organize.md +61 -441
  31. claude_mpm/commands/mpm-postmortem.md +6 -108
  32. claude_mpm/commands/mpm-session-resume.md +12 -363
  33. claude_mpm/commands/mpm-status.md +5 -69
  34. claude_mpm/commands/mpm-ticket-view.md +52 -495
  35. claude_mpm/commands/mpm-version.md +5 -107
  36. claude_mpm/core/config.py +2 -4
  37. claude_mpm/core/framework/loaders/agent_loader.py +1 -1
  38. claude_mpm/core/framework/loaders/instruction_loader.py +52 -11
  39. claude_mpm/core/optimized_startup.py +61 -0
  40. claude_mpm/core/shared/config_loader.py +3 -1
  41. claude_mpm/core/unified_agent_registry.py +1 -1
  42. claude_mpm/dashboard/static/svelte-build/_app/env.js +1 -0
  43. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.DWzvg0-y.css +1 -0
  44. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.ThTw9_ym.css +1 -0
  45. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/4TdZjIqw.js +1 -0
  46. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/5shd3_w0.js +24 -0
  47. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/B0uc0UOD.js +36 -0
  48. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/B7RN905-.js +1 -0
  49. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/B7xVLGWV.js +2 -0
  50. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BIF9m_hv.js +61 -0
  51. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BKjSRqUr.js +1 -0
  52. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BPYeabCQ.js +1 -0
  53. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BQaXIfA_.js +331 -0
  54. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BSNlmTZj.js +1 -0
  55. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Be7GpZd6.js +7 -0
  56. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Bh0LDWpI.js +145 -0
  57. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BofRWZRR.js +10 -0
  58. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BovzEFCE.js +30 -0
  59. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C30mlcqg.js +165 -0
  60. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C4B-KCzX.js +1 -0
  61. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C4JcI4KD.js +122 -0
  62. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CBBdVcY8.js +1 -0
  63. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CDuw-vjf.js +1 -0
  64. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C_Usid8X.js +15 -0
  65. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Cfqx1Qun.js +10 -0
  66. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CiIAseT4.js +128 -0
  67. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CmKTTxBW.js +1 -0
  68. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CnA0NrzZ.js +1 -0
  69. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Cs_tUR18.js +24 -0
  70. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Cu_Erd72.js +261 -0
  71. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CyWMqx4W.js +43 -0
  72. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CzZX-COe.js +220 -0
  73. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CzeYkLYB.js +65 -0
  74. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/D3k0OPJN.js +4 -0
  75. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/D9lljYKQ.js +1 -0
  76. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DGkLK5U1.js +267 -0
  77. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DI7hHRFL.js +1 -0
  78. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DLVjFsZ3.js +139 -0
  79. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DUrLdbGD.js +89 -0
  80. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DVp1hx9R.js +1 -0
  81. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DY1XQ8fi.js +2 -0
  82. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DZX00Y4g.js +1 -0
  83. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Da0KfYnO.js +1 -0
  84. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DaimHw_p.js +68 -0
  85. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Dfy6j1xT.js +323 -0
  86. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Dhb8PKl3.js +1 -0
  87. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Dle-35c7.js +64 -0
  88. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DmxopI1J.js +1 -0
  89. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DwBR2MJi.js +60 -0
  90. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/GYwsonyD.js +1 -0
  91. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Gi6I4Gst.js +1 -0
  92. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/NqQ1dWOy.js +1 -0
  93. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/RJiighC3.js +1 -0
  94. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Vzk33B_K.js +2 -0
  95. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/ZGh7QtNv.js +7 -0
  96. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/bT1r9zLR.js +1 -0
  97. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/bTOqqlTd.js +1 -0
  98. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/eNVUfhuA.js +1 -0
  99. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/iEWssX7S.js +162 -0
  100. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/sQeU3Y1z.js +1 -0
  101. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/uuIeMWc-.js +1 -0
  102. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/app.D6-I5TpK.js +2 -0
  103. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.NWzMBYRp.js +1 -0
  104. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/0.m1gL8KXf.js +1 -0
  105. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/1.CgNOuw-d.js +1 -0
  106. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.C0GcWctS.js +1 -0
  107. claude_mpm/dashboard/static/svelte-build/_app/version.json +1 -0
  108. claude_mpm/dashboard/static/svelte-build/favicon.svg +7 -0
  109. claude_mpm/dashboard/static/svelte-build/index.html +36 -0
  110. claude_mpm/dashboard-svelte/node_modules/katex/src/fonts/generate_fonts.py +58 -0
  111. claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/extract_tfms.py +114 -0
  112. claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/extract_ttfs.py +122 -0
  113. claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/format_json.py +28 -0
  114. claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/parse_tfm.py +211 -0
  115. claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-311.pyc +0 -0
  116. claude_mpm/hooks/claude_hooks/__pycache__/correlation_manager.cpython-311.pyc +0 -0
  117. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-311.pyc +0 -0
  118. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-311.pyc +0 -0
  119. claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-311.pyc +0 -0
  120. claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-311.pyc +0 -0
  121. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-311.pyc +0 -0
  122. claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-311.pyc +0 -0
  123. claude_mpm/hooks/claude_hooks/hook_handler.py +149 -1
  124. claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-311.pyc +0 -0
  125. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-311.pyc +0 -0
  126. claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-311.pyc +0 -0
  127. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-311.pyc +0 -0
  128. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-311.pyc +0 -0
  129. claude_mpm/hooks/claude_hooks/services/connection_manager.py +26 -6
  130. claude_mpm/hooks/kuzu_memory_hook.py +5 -5
  131. claude_mpm/init.py +276 -0
  132. claude_mpm/models/git_repository.py +3 -3
  133. claude_mpm/scripts/start_activity_logging.py +0 -0
  134. claude_mpm/services/agents/agent_builder.py +3 -3
  135. claude_mpm/services/agents/cache_git_manager.py +6 -6
  136. claude_mpm/services/agents/deployment/agent_deployment.py +29 -7
  137. claude_mpm/services/agents/deployment/agent_discovery_service.py +4 -2
  138. claude_mpm/services/agents/deployment/agent_format_converter.py +25 -13
  139. claude_mpm/services/agents/deployment/agent_template_builder.py +31 -19
  140. claude_mpm/services/agents/deployment/agents_directory_resolver.py +2 -2
  141. claude_mpm/services/agents/deployment/async_agent_deployment.py +31 -27
  142. claude_mpm/services/agents/deployment/local_template_deployment.py +3 -1
  143. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +169 -26
  144. claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +98 -75
  145. claude_mpm/services/agents/git_source_manager.py +23 -4
  146. claude_mpm/services/agents/recommender.py +5 -3
  147. claude_mpm/services/agents/single_tier_deployment_service.py +2 -2
  148. claude_mpm/services/agents/sources/git_source_sync_service.py +121 -10
  149. claude_mpm/services/agents/startup_sync.py +22 -2
  150. claude_mpm/services/diagnostics/checks/agent_check.py +2 -2
  151. claude_mpm/services/diagnostics/checks/agent_sources_check.py +1 -1
  152. claude_mpm/services/git/git_operations_service.py +8 -8
  153. claude_mpm/services/monitor/management/lifecycle.py +7 -1
  154. claude_mpm/services/monitor/server.py +473 -3
  155. claude_mpm/services/pm_skills_deployer.py +711 -0
  156. claude_mpm/services/profile_manager.py +337 -0
  157. claude_mpm/services/skills/git_skill_source_manager.py +148 -11
  158. claude_mpm/services/skills/selective_skill_deployer.py +97 -48
  159. claude_mpm/services/skills_deployer.py +161 -65
  160. claude_mpm/services/socketio/dashboard_server.py +1 -0
  161. claude_mpm/services/socketio/event_normalizer.py +37 -6
  162. claude_mpm/services/socketio/server/core.py +262 -123
  163. claude_mpm/skills/bundled/security-scanning.md +112 -0
  164. claude_mpm/skills/skill_manager.py +98 -3
  165. claude_mpm/templates/.pre-commit-config.yaml +112 -0
  166. claude_mpm/utils/agent_dependency_loader.py +14 -2
  167. claude_mpm/utils/agent_filters.py +1 -1
  168. claude_mpm/utils/migration.py +4 -4
  169. claude_mpm/utils/robust_installer.py +47 -3
  170. {claude_mpm-5.4.21.dist-info → claude_mpm-5.4.59.dist-info}/METADATA +7 -4
  171. {claude_mpm-5.4.21.dist-info → claude_mpm-5.4.59.dist-info}/RECORD +175 -81
  172. {claude_mpm-5.4.21.dist-info → claude_mpm-5.4.59.dist-info}/WHEEL +0 -0
  173. {claude_mpm-5.4.21.dist-info → claude_mpm-5.4.59.dist-info}/entry_points.txt +0 -0
  174. {claude_mpm-5.4.21.dist-info → claude_mpm-5.4.59.dist-info}/licenses/LICENSE +0 -0
  175. {claude_mpm-5.4.21.dist-info → claude_mpm-5.4.59.dist-info}/licenses/LICENSE-FAQ.md +0 -0
  176. {claude_mpm-5.4.21.dist-info → claude_mpm-5.4.59.dist-info}/top_level.txt +0 -0
claude_mpm/cli/startup.py CHANGED
@@ -10,7 +10,6 @@ Part of cli/__init__.py refactoring to reduce file size and improve modularity.
10
10
 
11
11
  import os
12
12
  import sys
13
- import warnings
14
13
  from pathlib import Path
15
14
 
16
15
 
@@ -60,43 +59,79 @@ def sync_hooks_on_startup(quiet: bool = False) -> bool:
60
59
  return False
61
60
 
62
61
 
63
- def check_legacy_cache() -> None:
64
- """Check for legacy cache/agents/ directory and warn user.
62
+ def cleanup_legacy_agent_cache() -> None:
63
+ """Remove legacy hierarchical agent cache directories.
64
+
65
+ WHY: Old agent cache used category-based directory structure directly in cache.
66
+ New structure uses remote source paths. This cleanup prevents confusion from
67
+ stale cache directories.
65
68
 
66
- WHY: cache/agents/ is deprecated in favor of cache/remote-agents/.
67
- Research confirmed that cache/remote-agents/ is the canonical location
68
- with 26 active code references, while cache/agents/ has only 7 legacy references.
69
+ Old structure (removed):
70
+ ~/.claude-mpm/cache/agents/engineer/
71
+ ~/.claude-mpm/cache/agents/ops/
72
+ ~/.claude-mpm/cache/agents/qa/
73
+ ...
69
74
 
70
- DESIGN DECISIONS:
71
- - Non-blocking warning: Doesn't stop execution, just informs user
72
- - Migration guidance: Provides clear path to migrate
73
- - One-time check: Only warns if legacy cache contains files
75
+ New structure (kept):
76
+ ~/.claude-mpm/cache/agents/bobmatnyc/claude-mpm-agents/agents/...
77
+
78
+ DESIGN DECISION: Runs early in startup before agent deployment to ensure
79
+ clean cache state. Removes only known legacy directories to avoid deleting
80
+ user data.
74
81
  """
75
- home = Path.home()
76
- legacy_cache = home / ".claude-mpm" / "cache" / "agents"
77
- canonical_cache = home / ".claude-mpm" / "cache" / "remote-agents"
78
- migration_marker = home / ".claude-mpm" / "cache" / ".migrated_to_remote_agents"
82
+ import shutil
83
+ from pathlib import Path
79
84
 
80
- # Skip if already migrated or no legacy cache
81
- if migration_marker.exists() or not legacy_cache.exists():
82
- return
85
+ from ..core.logger import get_logger
86
+
87
+ logger = get_logger("startup")
83
88
 
84
- # Check if legacy cache has actual agent files
85
- legacy_files = list(legacy_cache.glob("*.md")) + list(legacy_cache.glob("*.json"))
86
- if not legacy_files:
89
+ cache_dir = Path.home() / ".claude-mpm" / "cache" / "agents"
90
+ if not cache_dir.exists():
87
91
  return
88
92
 
89
- # Only warn if canonical cache doesn't exist (indicating unmigrated system)
90
- if not canonical_cache.exists():
91
- warnings.warn(
92
- f"\n⚠️ DEPRECATION: Legacy cache directory detected\n"
93
- f" Location: {legacy_cache}\n"
94
- f" Files found: {len(legacy_files)}\n\n"
95
- f"The 'cache/agents/' directory is deprecated. Please migrate to 'cache/remote-agents/'.\n"
96
- f"Run: python scripts/migrate_cache_to_remote_agents.py\n",
97
- DeprecationWarning,
98
- stacklevel=2,
99
- )
93
+ # Known legacy category directories (from old hierarchical structure)
94
+ legacy_dirs = [
95
+ "claude-mpm",
96
+ "documentation",
97
+ "engineer",
98
+ "ops",
99
+ "qa",
100
+ "security",
101
+ "universal",
102
+ ]
103
+
104
+ removed = []
105
+
106
+ # Remove legacy category directories
107
+ for dir_name in legacy_dirs:
108
+ legacy_path = cache_dir / dir_name
109
+ if legacy_path.exists() and legacy_path.is_dir():
110
+ try:
111
+ shutil.rmtree(legacy_path)
112
+ removed.append(dir_name)
113
+ except Exception as e:
114
+ logger.debug(f"Failed to remove legacy directory {dir_name}: {e}")
115
+
116
+ # Also remove stray BASE-AGENT.md in cache root
117
+ base_agent = cache_dir / "BASE-AGENT.md"
118
+ if base_agent.exists():
119
+ try:
120
+ base_agent.unlink()
121
+ removed.append("BASE-AGENT.md")
122
+ except Exception as e:
123
+ logger.debug(f"Failed to remove BASE-AGENT.md: {e}")
124
+
125
+ if removed:
126
+ logger.info(f"Cleaned up legacy agent cache: {', '.join(removed)}")
127
+
128
+
129
+ def check_legacy_cache() -> None:
130
+ """Deprecated: Legacy cache checking is no longer needed.
131
+
132
+ This function is kept for backward compatibility but does nothing.
133
+ All agent cache operations now use the standardized cache/agents/ directory.
134
+ """
100
135
 
101
136
 
102
137
  def setup_early_environment(argv):
@@ -431,19 +466,48 @@ def sync_remote_agents_on_startup():
431
466
  block startup to ensure claude-mpm remains functional.
432
467
 
433
468
  Workflow:
434
- 1. Sync all enabled Git sources (download/cache files) - Phase 1 progress bar
435
- 2. Deploy agents to ~/.claude/agents/ - Phase 2 progress bar
436
- 3. Cleanup orphaned agents (ours but no longer deployed) - Phase 3
437
- 4. Log deployment results
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
473
+ 5. Log deployment results
438
474
  """
439
- # Check for legacy cache and warn user if found
475
+ # Cleanup legacy cache directories first (before syncing)
476
+ cleanup_legacy_agent_cache()
477
+
478
+ # DEPRECATED: Legacy warning - replaced by automatic cleanup above
440
479
  check_legacy_cache()
441
480
 
442
481
  try:
482
+ # Load active profile if configured
483
+ # Get project root (where .claude-mpm exists)
484
+ from pathlib import Path
485
+
486
+ from ..core.shared.config_loader import ConfigLoader
443
487
  from ..services.agents.deployment.agent_deployment import AgentDeploymentService
444
488
  from ..services.agents.startup_sync import sync_agents_on_startup
489
+ from ..services.profile_manager import ProfileManager
445
490
  from ..utils.progress import ProgressBar
446
491
 
492
+ project_root = Path.cwd()
493
+
494
+ profile_manager = ProfileManager(project_dir=project_root)
495
+ config_loader = ConfigLoader()
496
+ main_config = config_loader.load_main_config()
497
+ active_profile = main_config.get("active_profile")
498
+
499
+ if active_profile:
500
+ success = profile_manager.load_profile(active_profile)
501
+ if success:
502
+ summary = profile_manager.get_filtering_summary()
503
+ from ..core.logger import get_logger
504
+
505
+ logger = get_logger("cli")
506
+ logger.info(
507
+ f"Profile '{active_profile}' active: "
508
+ f"{summary['enabled_agents_count']} agents enabled"
509
+ )
510
+
447
511
  # Phase 1: Sync files from Git sources
448
512
  result = sync_agents_on_startup()
449
513
 
@@ -470,19 +534,132 @@ def sync_remote_agents_on_startup():
470
534
  # Phase 2: Deploy agents from cache to ~/.claude/agents/
471
535
  # This mirrors the skills deployment pattern (lines 371-407)
472
536
  try:
473
- # Initialize deployment service
474
- deployment_service = AgentDeploymentService()
537
+ # Initialize deployment service with profile-filtered configuration
538
+ from ..core.config import Config
539
+
540
+ deploy_config = None
541
+ if active_profile and profile_manager.active_profile:
542
+ # Create config with excluded agents based on profile
543
+ # Get all agents that should be excluded (not in enabled list)
544
+ from pathlib import Path
545
+
546
+ cache_dir = Path.home() / ".claude-mpm" / "cache" / "agents"
547
+ if cache_dir.exists():
548
+ # Find all agent files
549
+ # Supports both flat cache and {owner}/{repo}/agents/ structure
550
+ all_agent_files = [
551
+ f
552
+ for f in cache_dir.rglob("*.md")
553
+ if "/agents/" in str(f)
554
+ and f.stem.lower() != "base-agent"
555
+ and f.name.lower()
556
+ not in {"readme.md", "changelog.md", "contributing.md"}
557
+ ]
558
+
559
+ # Build exclusion list for agents not in profile
560
+ excluded_agents = []
561
+ for agent_file in all_agent_files:
562
+ agent_name = agent_file.stem
563
+ if not profile_manager.is_agent_enabled(agent_name):
564
+ excluded_agents.append(agent_name)
565
+
566
+ if excluded_agents:
567
+ # Get singleton config and update with profile settings
568
+ # BUGFIX: Config is a singleton that ignores dict parameter if already initialized.
569
+ # Creating Config({...}) doesn't store excluded_agents - use set() instead.
570
+ deploy_config = Config()
571
+ deploy_config.set(
572
+ "agent_deployment.excluded_agents", excluded_agents
573
+ )
574
+ deploy_config.set(
575
+ "agent_deployment.filter_non_mpm_agents", False
576
+ )
577
+ deploy_config.set("agent_deployment.case_sensitive", False)
578
+ deploy_config.set(
579
+ "agent_deployment.exclude_dependencies", False
580
+ )
581
+ logger.info(
582
+ f"Profile '{active_profile}': Excluding {len(excluded_agents)} agents from deployment"
583
+ )
584
+
585
+ deployment_service = AgentDeploymentService(config=deploy_config)
475
586
 
476
587
  # Count agents in cache to show accurate progress
477
588
  from pathlib import Path
478
589
 
479
- cache_dir = Path.home() / ".claude-mpm" / "cache" / "remote-agents"
590
+ cache_dir = Path.home() / ".claude-mpm" / "cache" / "agents"
480
591
  agent_count = 0
481
592
 
482
593
  if cache_dir.exists():
483
- # Count MD files in cache (agent markdown files from Git)
484
- # BUGFIX: Only count files in agent directories, not docs/templates/READMEs
485
- # Valid agent paths must contain "/agents/" or be in root-level category dirs
594
+ # BUGFIX (cache-count-inflation): Clean up stale cache files
595
+ # from old repositories before counting to prevent inflated counts.
596
+ # Issue: Old caches like bobmatnyc/claude-mpm-agents/agents/
597
+ # were counted alongside current agents, inflating count
598
+ # from 44 to 85.
599
+ #
600
+ # Solution: Remove files with nested /agents/ paths
601
+ # (e.g., cache/agents/user/repo/agents/...)
602
+ # Keep only current agents (e.g., cache/agents/engineer/...)
603
+ removed_count = 0
604
+ stale_dirs = set()
605
+
606
+ for md_file in cache_dir.rglob("*.md"):
607
+ # Stale cache files have multiple /agents/ in their path RELATIVE to cache_dir
608
+ # Current: cache/agents/bobmatnyc/claude-mpm-agents/agents/engineer/...
609
+ # (1 occurrence in relative path: /agents/)
610
+ # Old flat: cache/agents/engineer/...
611
+ # (0 occurrences in relative path - no repo structure)
612
+ # The issue: str(md_file).count("/agents/") counts BOTH cache/agents/ AND repo/agents/
613
+ # Fix: Count /agents/ in path RELATIVE to cache_dir (after cache/agents/)
614
+ relative_path = str(md_file.relative_to(cache_dir))
615
+ if relative_path.count("/agents/") > 1:
616
+ # Track parent directory for cleanup
617
+ # Extract subdirectory under cache/agents/
618
+ # (e.g., "bobmatnyc")
619
+ parts = md_file.parts
620
+ cache_agents_idx = parts.index("agents")
621
+ if cache_agents_idx + 1 < len(parts):
622
+ stale_subdir = parts[cache_agents_idx + 1]
623
+ # Only remove if it's not a known category directory
624
+ if stale_subdir not in [
625
+ "engineer",
626
+ "ops",
627
+ "qa",
628
+ "universal",
629
+ "documentation",
630
+ "claude-mpm",
631
+ "security",
632
+ ]:
633
+ stale_dirs.add(cache_dir / stale_subdir)
634
+
635
+ md_file.unlink()
636
+ removed_count += 1
637
+
638
+ # Remove empty stale directories
639
+ for stale_dir in stale_dirs:
640
+ if stale_dir.exists() and stale_dir.is_dir():
641
+ try:
642
+ # Remove directory and all contents
643
+ import shutil
644
+
645
+ shutil.rmtree(stale_dir)
646
+ except Exception:
647
+ pass # Ignore cleanup errors
648
+
649
+ if removed_count > 0:
650
+ from loguru import logger
651
+
652
+ logger.info(
653
+ f"Cleaned up {removed_count} stale cache files "
654
+ f"from old repositories"
655
+ )
656
+
657
+ # Count MD files in cache (agent markdown files from
658
+ # current repos)
659
+ # BUGFIX: Only count files in agent directories,
660
+ # not docs/templates/READMEs
661
+ # Valid agent paths must contain "/agents/" exactly ONCE
662
+ # (current structure)
486
663
  # Exclude PM templates, BASE-AGENT, and documentation files
487
664
  pm_templates = {
488
665
  "base-agent.md",
@@ -505,25 +682,30 @@ def sync_remote_agents_on_startup():
505
682
  "auto-deploy-index.md",
506
683
  }
507
684
 
508
- # Find all markdown files
685
+ # Find all markdown files (after cleanup)
509
686
  all_md_files = list(cache_dir.rglob("*.md"))
510
687
 
511
688
  # Filter to only agent files:
512
- # 1. Must have "/agents/" in path (from git repos)
689
+ # 1. Must have "/agents/" in path (current structure supports
690
+ # both flat and {owner}/{repo}/agents/ patterns)
513
691
  # 2. Must not be in PM templates or doc files
514
692
  # 3. Exclude BASE-AGENT.md which is not a deployable agent
515
- # 4. Exclude build artifacts (dist/, build/, .cache/) to prevent double-counting
693
+ # 4. Exclude build artifacts (dist/, build/, .cache/)
694
+ # to prevent double-counting
516
695
  agent_files = [
517
696
  f
518
697
  for f in all_md_files
519
698
  if (
520
- # Must be in an agent directory (from git repos like bobmatnyc/claude-mpm-agents/agents/)
699
+ # Must be in an agent directory
700
+ # Supports: cache/agents/{category}/... (flat)
701
+ # Supports: cache/agents/{owner}/{repo}/agents/{category}/... (GitHub sync)
521
702
  "/agents/" in str(f)
522
703
  # Exclude PM templates, doc files, and BASE-AGENT
523
704
  and f.name.lower() not in pm_templates
524
705
  and f.name.lower() not in doc_files
525
706
  and f.name.lower() != "base-agent.md"
526
- # Exclude build artifacts (prevents double-counting source + built files)
707
+ # Exclude build artifacts (prevents double-counting
708
+ # source + built files)
527
709
  and not any(
528
710
  part in str(f).split("/")
529
711
  for part in ["dist", "build", ".cache"]
@@ -539,6 +721,7 @@ def sync_remote_agents_on_startup():
539
721
  target_dir=deploy_target,
540
722
  force_rebuild=False, # Only deploy if versions differ
541
723
  deployment_mode="update", # Version-aware updates
724
+ config=deploy_config, # Pass config to respect profile filtering
542
725
  )
543
726
 
544
727
  # Get actual counts from deployment result (reflects configured agents)
@@ -594,27 +777,27 @@ def sync_remote_agents_on_startup():
594
777
  )
595
778
 
596
779
  # Show total configured agents (deployed + updated + already existing)
597
- # Include repo count for context and removed count if any
780
+ # Include cache count for context and removed count if any
598
781
  if deployed > 0 or updated > 0:
599
782
  if removed > 0:
600
783
  deploy_progress.finish(
601
784
  f"Complete: {deployed} new, {updated} updated, {skipped} unchanged, "
602
- f"{removed} removed ({total_configured} configured from {agent_count} in repo)"
785
+ f"{removed} removed ({total_configured} configured from {agent_count} files in cache)"
603
786
  )
604
787
  else:
605
788
  deploy_progress.finish(
606
789
  f"Complete: {deployed} new, {updated} updated, {skipped} unchanged "
607
- f"({total_configured} configured from {agent_count} in repo)"
790
+ f"({total_configured} configured from {agent_count} files in cache)"
608
791
  )
609
792
  elif removed > 0:
610
793
  deploy_progress.finish(
611
- f"Complete: {total_configured} agents ready - all unchanged, "
612
- f"{removed} removed ({agent_count} available in repo)"
794
+ f"Complete: {total_configured} agents deployed, "
795
+ f"{removed} removed ({agent_count} files in cache)"
613
796
  )
614
797
  else:
615
798
  deploy_progress.finish(
616
- f"Complete: {total_configured} agents ready - all unchanged "
617
- f"({agent_count} available in repo)"
799
+ f"Complete: {total_configured} agents deployed "
800
+ f"({agent_count} files in cache)"
618
801
  )
619
802
 
620
803
  # Display deployment errors to user (not just logs)
@@ -679,13 +862,16 @@ def sync_remote_skills_on_startup():
679
862
  1. Sync all enabled Git sources (download/cache files) - Phase 1 progress bar
680
863
  2. Scan deployed agents for skill requirements → save to configuration.yaml
681
864
  3. Resolve which skills to deploy (user_defined vs agent_referenced)
682
- 4. Deploy resolved skills to ~/.claude/skills/ - Phase 2 progress bar
683
- 5. Log deployment results with source indication
865
+ 4. Apply profile filtering if active
866
+ 5. Deploy resolved skills to ~/.claude/skills/ - Phase 2 progress bar
867
+ 6. Log deployment results with source indication
684
868
  """
685
869
  try:
686
870
  from pathlib import Path
687
871
 
688
872
  from ..config.skill_sources import SkillSourceConfiguration
873
+ from ..core.shared.config_loader import ConfigLoader
874
+ from ..services.profile_manager import ProfileManager
689
875
  from ..services.skills.git_skill_source_manager import GitSkillSourceManager
690
876
  from ..services.skills.selective_skill_deployer import (
691
877
  get_required_skills_from_agents,
@@ -694,6 +880,28 @@ def sync_remote_skills_on_startup():
694
880
  )
695
881
  from ..utils.progress import ProgressBar
696
882
 
883
+ # Load active profile if configured
884
+ # Get project root (where .claude-mpm exists)
885
+ project_root = Path.cwd()
886
+
887
+ profile_manager = ProfileManager(project_dir=project_root)
888
+ config_loader = ConfigLoader()
889
+ main_config = config_loader.load_main_config()
890
+ active_profile = main_config.get("active_profile")
891
+
892
+ if active_profile:
893
+ success = profile_manager.load_profile(active_profile)
894
+ if success:
895
+ from ..core.logger import get_logger
896
+
897
+ logger = get_logger("cli")
898
+ summary = profile_manager.get_filtering_summary()
899
+ logger.info(
900
+ f"Profile '{active_profile}' active: "
901
+ f"{summary['enabled_skills_count']} skills enabled, "
902
+ f"{summary['disabled_patterns_count']} patterns disabled"
903
+ )
904
+
697
905
  config = SkillSourceConfiguration()
698
906
  manager = GitSkillSourceManager(config)
699
907
 
@@ -782,108 +990,191 @@ def sync_remote_skills_on_startup():
782
990
 
783
991
  # Phase 2: Scan agents and save to configuration.yaml
784
992
  # This step populates configuration.yaml with agent-referenced skills
785
- if results["synced_count"] > 0:
786
- agents_dir = Path.cwd() / ".claude" / "agents"
787
-
788
- # Scan agents for skill requirements
789
- agent_skills = get_required_skills_from_agents(agents_dir)
790
-
791
- # Save to project-level configuration.yaml
792
- project_config_path = Path.cwd() / ".claude-mpm" / "configuration.yaml"
793
- save_agent_skills_to_config(list(agent_skills), project_config_path)
993
+ # CRITICAL: Always scan agents to populate agent_referenced, even when using cached skills.
994
+ # Without this, skill_filter=None causes ALL skills to deploy and NO cleanup to run.
995
+ agents_dir = Path.cwd() / ".claude" / "agents"
996
+
997
+ # Scan agents for skill requirements (ALWAYS run to ensure cleanup works)
998
+ agent_skills = get_required_skills_from_agents(agents_dir)
999
+ logger.info(
1000
+ f"Agent scan found {len(agent_skills)} unique skills across deployed agents"
1001
+ )
794
1002
 
795
- # Phase 3: Resolve which skills to deploy (user_defined or agent_referenced)
796
- skills_to_deploy, skill_source = get_skills_to_deploy(project_config_path)
1003
+ # Save to project-level configuration.yaml
1004
+ project_config_path = Path.cwd() / ".claude-mpm" / "configuration.yaml"
1005
+ save_agent_skills_to_config(list(agent_skills), project_config_path)
1006
+ logger.debug(
1007
+ f"Saved {len(agent_skills)} agent-referenced skills to {project_config_path}"
1008
+ )
797
1009
 
798
- # Get all skills to determine counts
799
- all_skills = manager.get_all_skills()
800
- total_skill_count = len(all_skills)
1010
+ # Phase 3: Resolve which skills to deploy (user_defined or agent_referenced)
1011
+ skills_to_deploy, skill_source = get_skills_to_deploy(project_config_path)
801
1012
 
802
- # Determine skill count based on resolution
803
- skill_count = (
804
- len(skills_to_deploy) if skills_to_deploy else total_skill_count
1013
+ # CRITICAL DEBUG: Log deployment resolution to diagnose cleanup issues
1014
+ if skills_to_deploy:
1015
+ logger.info(
1016
+ f"Resolved {len(skills_to_deploy)} skills from {skill_source} (cleanup will run)"
1017
+ )
1018
+ else:
1019
+ logger.warning(
1020
+ f"No skills resolved from {skill_source} - will deploy ALL skills WITHOUT cleanup! "
1021
+ f"This may indicate agent_referenced is empty in configuration.yaml."
805
1022
  )
806
1023
 
807
- if skill_count > 0:
808
- # Deploy skills with resolved filter
809
- # Deploy to project directory (like agents), not user directory
810
- deployment_result = manager.deploy_skills(
811
- target_dir=Path.cwd() / ".claude" / "skills",
812
- force=False,
813
- skill_filter=set(skills_to_deploy) if skills_to_deploy else None,
1024
+ # Phase 4: Apply profile filtering if active
1025
+ if active_profile and profile_manager.active_profile:
1026
+ # Filter skills based on profile
1027
+ if skills_to_deploy:
1028
+ # Filter the resolved skill list
1029
+ original_count = len(skills_to_deploy)
1030
+ filtered_skills = [
1031
+ skill
1032
+ for skill in skills_to_deploy
1033
+ if profile_manager.is_skill_enabled(skill)
1034
+ ]
1035
+ filtered_count = original_count - len(filtered_skills)
1036
+
1037
+ # SAFEGUARD: Warn if all skills were filtered out (misconfiguration)
1038
+ if not filtered_skills and original_count > 0:
1039
+ logger.warning(
1040
+ f"Profile '{active_profile}' filtered ALL {original_count} skills. "
1041
+ f"This may indicate a naming mismatch in the profile."
1042
+ )
1043
+ elif filtered_count > 0:
1044
+ logger.info(
1045
+ f"Profile '{active_profile}' filtered {filtered_count} skills "
1046
+ f"({len(filtered_skills)} remaining)"
1047
+ )
1048
+
1049
+ skills_to_deploy = filtered_skills
1050
+ skill_source = f"{skill_source} + profile filtered"
1051
+ else:
1052
+ # No explicit skill list - filter from all available
1053
+ all_skills = manager.get_all_skills()
1054
+ filtered_skills = [
1055
+ skill["name"]
1056
+ for skill in all_skills
1057
+ if profile_manager.is_skill_enabled(skill["name"])
1058
+ ]
1059
+ skills_to_deploy = filtered_skills
1060
+ skill_source = "profile filtered"
1061
+ logger.info(
1062
+ f"Profile '{active_profile}': "
1063
+ f"{len(filtered_skills)} skills enabled from {len(all_skills)} available"
814
1064
  )
815
1065
 
816
- # Get actual counts from deployment result
817
- deployed = deployment_result.get("deployed_count", 0)
818
- skipped = deployment_result.get("skipped_count", 0)
819
- filtered = deployment_result.get("filtered_count", 0)
820
- total_available = deployed + skipped
1066
+ # Get all skills to determine counts
1067
+ all_skills = manager.get_all_skills()
1068
+ total_skill_count = len(all_skills)
821
1069
 
822
- # Only show progress bar if there are skills to deploy
823
- if total_available > 0:
824
- deploy_progress = ProgressBar(
825
- total=total_available,
826
- prefix="Deploying skill directories",
827
- show_percentage=True,
828
- show_counter=True,
829
- )
830
- # Update progress bar to completion
831
- deploy_progress.update(total_available)
832
- else:
833
- # No skills to deploy - create dummy progress for message only
834
- deploy_progress = ProgressBar(
835
- total=1,
836
- prefix="Deploying skill directories",
837
- show_percentage=False,
838
- show_counter=False,
839
- )
840
- deploy_progress.update(1)
1070
+ # Determine skill count based on resolution
1071
+ skill_count = (
1072
+ len(skills_to_deploy) if skills_to_deploy else total_skill_count
1073
+ )
1074
+
1075
+ if skill_count > 0:
1076
+ # Deploy skills with resolved filter
1077
+ # Deploy ONLY to project directory (not user-level)
1078
+ # DESIGN DECISION: Project-level deployment keeps skills isolated per project,
1079
+ # avoiding pollution of user's global ~/.claude/skills/ directory.
1080
+
1081
+ # Deploy to project-local directory with cleanup
1082
+ deployment_result = manager.deploy_skills(
1083
+ target_dir=Path.cwd() / ".claude" / "skills",
1084
+ force=False,
1085
+ # CRITICAL FIX: Empty list should mean "deploy no skills", not "deploy all"
1086
+ # When skills_to_deploy is [], we want skill_filter=set() NOT skill_filter=None
1087
+ # None means "no filtering" (deploy all), empty set means "filter to nothing"
1088
+ skill_filter=set(skills_to_deploy) if skills_to_deploy is not None else None,
1089
+ )
841
1090
 
842
- # Show total available skills (deployed + already existing)
843
- # Include source indication (user_defined vs agent_referenced)
844
- # Note: total_skill_count is from the repo, total_available is what's deployed/needed
845
- source_label = (
846
- "user override" if skill_source == "user_defined" else "from agents"
1091
+ # REMOVED: User-level deployment (lines 1068-1074)
1092
+ # Reason: Skills should be project-specific, not user-global.
1093
+ # Claude Code can read from project-level .claude/skills/ directory.
1094
+
1095
+ # Get actual counts from deployment result (use project-local for display)
1096
+ deployed = deployment_result.get("deployed_count", 0)
1097
+ skipped = deployment_result.get("skipped_count", 0)
1098
+ filtered = deployment_result.get("filtered_count", 0)
1099
+ removed = deployment_result.get("removed_count", 0)
1100
+ total_available = deployed + skipped
1101
+
1102
+ # Only show progress bar if there are skills to deploy
1103
+ if total_available > 0:
1104
+ deploy_progress = ProgressBar(
1105
+ total=total_available,
1106
+ prefix="Deploying skill directories",
1107
+ show_percentage=True,
1108
+ show_counter=True,
847
1109
  )
1110
+ # Update progress bar to completion
1111
+ deploy_progress.update(total_available)
1112
+ else:
1113
+ # No skills to deploy - create dummy progress for message only
1114
+ deploy_progress = ProgressBar(
1115
+ total=1,
1116
+ prefix="Deploying skill directories",
1117
+ show_percentage=False,
1118
+ show_counter=False,
1119
+ )
1120
+ deploy_progress.update(1)
1121
+
1122
+ # Show total available skills (deployed + already existing)
1123
+ # Include source indication (user_defined vs agent_referenced)
1124
+ # Note: total_skill_count is from cache, total_available is what's deployed/needed
1125
+ source_label = (
1126
+ "user override" if skill_source == "user_defined" else "from agents"
1127
+ )
848
1128
 
1129
+ # Build finish message with cleanup info
1130
+ if deployed > 0 or removed > 0:
1131
+ parts = []
849
1132
  if deployed > 0:
850
- if filtered > 0:
851
- deploy_progress.finish(
852
- f"Complete: {deployed} new, {skipped} unchanged "
853
- f"({total_available} {source_label}, {filtered} available in repo)"
854
- )
855
- else:
856
- deploy_progress.finish(
857
- f"Complete: {deployed} new, {skipped} unchanged "
858
- f"({total_available} skills {source_label} from {total_skill_count} in repo)"
859
- )
860
- elif filtered > 0:
861
- # Skills filtered means agents require fewer skills than available
1133
+ parts.append(f"{deployed} new")
1134
+ if skipped > 0:
1135
+ parts.append(f"{skipped} unchanged")
1136
+ if removed > 0:
1137
+ parts.append(f"{removed} removed")
1138
+
1139
+ status = ", ".join(parts)
1140
+
1141
+ if filtered > 0:
862
1142
  deploy_progress.finish(
863
- f"No skills needed ({source_label}, {total_skill_count} available in repo)"
1143
+ f"Complete: {status} ({total_available} {source_label}, {filtered} files in cache)"
864
1144
  )
865
1145
  else:
866
1146
  deploy_progress.finish(
867
- f"Complete: {total_available} skills {source_label} "
868
- f"({total_skill_count} available in repo)"
1147
+ f"Complete: {status} ({total_available} skills {source_label} from {total_skill_count} files in cache)"
869
1148
  )
1149
+ elif filtered > 0:
1150
+ # Skills filtered means agents require fewer skills than available
1151
+ deploy_progress.finish(
1152
+ f"No skills needed ({source_label}, {total_skill_count} files in cache)"
1153
+ )
1154
+ else:
1155
+ # No changes - all skills already deployed
1156
+ msg = f"Complete: {total_available} skills {source_label}"
1157
+ if removed > 0:
1158
+ msg += f", {removed} removed"
1159
+ msg += f" ({total_skill_count} files in cache)"
1160
+ deploy_progress.finish(msg)
1161
+
1162
+ # Log deployment errors if any
1163
+ from ..core.logger import get_logger
870
1164
 
871
- # Log deployment errors if any
872
- from ..core.logger import get_logger
873
-
874
- logger = get_logger("cli")
1165
+ logger = get_logger("cli")
875
1166
 
876
- errors = deployment_result.get("errors", [])
877
- if errors:
878
- logger.warning(
879
- f"Skill deployment completed with {len(errors)} errors: {errors}"
880
- )
1167
+ errors = deployment_result.get("errors", [])
1168
+ if errors:
1169
+ logger.warning(
1170
+ f"Skill deployment completed with {len(errors)} errors: {errors}"
1171
+ )
881
1172
 
882
- # Log sync errors if any
883
- if results["failed_count"] > 0:
884
- logger.warning(
885
- f"Skill sync completed with {results['failed_count']} failures"
886
- )
1173
+ # Log sync errors if any
1174
+ if results["failed_count"] > 0:
1175
+ logger.warning(
1176
+ f"Skill sync completed with {results['failed_count']} failures"
1177
+ )
887
1178
 
888
1179
  except Exception as e:
889
1180
  # Non-critical - log but don't fail startup
@@ -921,7 +1212,7 @@ def show_agent_summary():
921
1212
  installed_count = len(agent_files)
922
1213
 
923
1214
  # Count available agents in cache (from remote sources)
924
- cache_dir = Path.home() / ".claude-mpm" / "cache" / "remote-agents"
1215
+ cache_dir = Path.home() / ".claude-mpm" / "cache" / "agents"
925
1216
  available_count = 0
926
1217
  if cache_dir.exists():
927
1218
  # Use same filtering logic as agent deployment (lines 486-533 in startup.py)
@@ -966,7 +1257,7 @@ def show_agent_summary():
966
1257
  # Display summary if we have agents
967
1258
  if installed_count > 0 or available_count > 0:
968
1259
  print(
969
- f"✓ Agents: {installed_count} installed / {available_count} available",
1260
+ f"✓ Agents: {installed_count} deployed / {max(0, available_count - installed_count)} cached",
970
1261
  flush=True,
971
1262
  )
972
1263
 
@@ -983,61 +1274,64 @@ def show_skill_summary():
983
1274
  Display skill availability summary on startup.
984
1275
 
985
1276
  WHY: Users should see at a glance how many skills are deployed and available
986
- from collections, similar to the agent summary.
1277
+ from cache, similar to the agent summary showing "X deployed / Y cached".
987
1278
 
988
- DESIGN DECISION: Fast, non-blocking check that counts skills from deployment
989
- directory and collection repos. Shows "X installed (Y available)" format.
1279
+ DESIGN DECISION: Fast, non-blocking check that counts skills from:
1280
+ - Deployed skills: PROJECT-level .claude/skills/ directory
1281
+ - Cached skills: ~/.claude-mpm/cache/skills/ directory (from remote sources)
1282
+
1283
+ Shows format: "✓ Skills: X deployed / Y cached"
990
1284
  Failures are silent to avoid blocking startup.
991
1285
  """
992
1286
  try:
993
1287
  from pathlib import Path
994
1288
 
995
- # Count deployed skills (installed)
996
- skills_dir = Path.home() / ".claude" / "skills"
997
- installed_count = 0
998
- if skills_dir.exists():
1289
+ # Count deployed skills (PROJECT-level, not user-level)
1290
+ project_skills_dir = Path.cwd() / ".claude" / "skills"
1291
+ deployed_count = 0
1292
+ if project_skills_dir.exists():
999
1293
  # Count directories with SKILL.md (excludes collection repos)
1000
1294
  # Exclude collection directories (obra-superpowers, etc.)
1001
1295
  skill_dirs = [
1002
1296
  d
1003
- for d in skills_dir.iterdir()
1297
+ for d in project_skills_dir.iterdir()
1004
1298
  if d.is_dir()
1005
1299
  and (d / "SKILL.md").exists()
1006
1300
  and not (d / ".git").exists() # Exclude collection repos
1007
1301
  ]
1008
- installed_count = len(skill_dirs)
1302
+ deployed_count = len(skill_dirs)
1009
1303
 
1010
- # Count available skills in collections
1011
- available_count = 0
1012
- if skills_dir.exists():
1013
- # Scan all collection directories (those with .git)
1014
- for collection_dir in skills_dir.iterdir():
1015
- if (
1016
- not collection_dir.is_dir()
1017
- or not (collection_dir / ".git").exists()
1018
- ):
1304
+ # Count cached skills (from remote sources, not deployed yet)
1305
+ # This matches the agent summary pattern: deployed vs cached
1306
+ cache_dir = Path.home() / ".claude-mpm" / "cache" / "skills"
1307
+ cached_count = 0
1308
+ if cache_dir.exists():
1309
+ # Scan all repository directories in cache
1310
+ # Cache structure: ~/.claude-mpm/cache/skills/{owner}/{repo}/...
1311
+ for repo_dir in cache_dir.rglob("*"):
1312
+ if not repo_dir.is_dir():
1019
1313
  continue
1020
1314
 
1021
- # Count skill directories in this collection
1315
+ # Count skill directories (those with SKILL.md)
1022
1316
  # Skills can be nested in: skills/category/skill-name/SKILL.md
1023
1317
  # or in flat structure: skill-name/SKILL.md
1024
- for root, dirs, files in os.walk(collection_dir):
1318
+ for root, dirs, files in os.walk(repo_dir):
1025
1319
  if "SKILL.md" in files:
1026
- # Exclude build artifacts and hidden directories (within the collection)
1027
- # Get relative path from collection_dir to avoid excluding based on .claude parent
1320
+ # Exclude build artifacts and hidden directories
1028
1321
  root_path = Path(root)
1029
- relative_parts = root_path.relative_to(collection_dir).parts
1030
1322
  if not any(
1031
1323
  part.startswith(".")
1032
1324
  or part in ["dist", "build", "__pycache__"]
1033
- for part in relative_parts
1325
+ for part in root_path.parts
1034
1326
  ):
1035
- available_count += 1
1327
+ cached_count += 1
1036
1328
 
1037
- # Display summary if we have skills
1038
- if installed_count > 0 or available_count > 0:
1329
+ # Display summary using agent summary format: "X deployed / Y cached"
1330
+ # Only show non-deployed cached skills (subtract deployed from cached)
1331
+ non_deployed_cached = max(0, cached_count - deployed_count)
1332
+ if deployed_count > 0 or non_deployed_cached > 0:
1039
1333
  print(
1040
- f"✓ Skills: {installed_count} installed ({available_count} available)",
1334
+ f"✓ Skills: {deployed_count} deployed / {non_deployed_cached} cached",
1041
1335
  flush=True,
1042
1336
  )
1043
1337
 
@@ -1049,6 +1343,45 @@ def show_skill_summary():
1049
1343
  logger.debug(f"Failed to generate skill summary: {e}")
1050
1344
 
1051
1345
 
1346
+ def verify_and_show_pm_skills():
1347
+ """Verify PM skills and display status.
1348
+
1349
+ WHY: PM skills are essential for PM agent operation.
1350
+ Shows deployment status and auto-deploys if missing.
1351
+ """
1352
+ try:
1353
+ from pathlib import Path
1354
+
1355
+ from ..services.pm_skills_deployer import PMSkillsDeployerService
1356
+
1357
+ deployer = PMSkillsDeployerService()
1358
+ project_dir = Path.cwd()
1359
+
1360
+ result = deployer.verify_pm_skills(project_dir)
1361
+
1362
+ if result.verified:
1363
+ # Show verified status
1364
+ print(f"✓ PM skills: {result.skill_count} verified", flush=True)
1365
+ else:
1366
+ # Auto-deploy if missing
1367
+ print("Deploying PM skills...", end="", flush=True)
1368
+ deploy_result = deployer.deploy_pm_skills(project_dir)
1369
+ if deploy_result.success:
1370
+ total = len(deploy_result.deployed) + len(deploy_result.skipped)
1371
+ print(f"\r✓ PM skills: {total} deployed" + " " * 20, flush=True)
1372
+ else:
1373
+ print("\r⚠ PM skills: deployment failed" + " " * 20, flush=True)
1374
+
1375
+ except ImportError:
1376
+ # PM skills deployer not available - skip silently
1377
+ pass
1378
+ except Exception as e:
1379
+ from ..core.logger import get_logger
1380
+
1381
+ logger = get_logger("cli")
1382
+ logger.debug(f"PM skills verification failed: {e}")
1383
+
1384
+
1052
1385
  def auto_install_chrome_devtools_on_startup():
1053
1386
  """
1054
1387
  Automatically install chrome-devtools-mcp on startup if enabled.
@@ -1123,6 +1456,7 @@ def run_background_services():
1123
1456
  sync_remote_skills_on_startup() # Override layer: Git-based skills (takes precedence)
1124
1457
  discover_and_link_runtime_skills() # Discovery: user-added skills
1125
1458
  show_skill_summary() # Display skill counts after deployment
1459
+ verify_and_show_pm_skills() # PM skills verification and status
1126
1460
 
1127
1461
  deploy_output_style_on_startup()
1128
1462
 
@@ -1256,18 +1590,10 @@ def verify_mcp_gateway_startup():
1256
1590
  DESIGN DECISION: This is non-blocking - failures are logged but don't prevent
1257
1591
  startup to ensure claude-mpm remains functional even if MCP gateway has issues.
1258
1592
  """
1259
- # Quick verification of MCP services installation
1260
- try:
1261
- from ..core.logger import get_logger
1262
- from ..services.mcp_service_verifier import verify_mcp_services_on_startup
1263
-
1264
- logger = get_logger("mcp_verify")
1265
- all_ok, message = verify_mcp_services_on_startup()
1266
- if not all_ok:
1267
- logger.warning(message)
1268
- except Exception:
1269
- # Non-critical - continue with startup
1270
- pass
1593
+ # DISABLED: MCP service verification removed - Claude Code handles MCP natively
1594
+ # The previous check warned about missing MCP services, but users should configure
1595
+ # MCP servers through Claude Code's native MCP management, not through claude-mpm.
1596
+ # See: https://docs.anthropic.com/en/docs/claude-code/mcp
1271
1597
 
1272
1598
  try:
1273
1599
  import asyncio