claude-mpm 5.4.55__py3-none-any.whl → 5.4.85__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 (173) 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 +63 -241
  4. claude_mpm/agents/CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md +109 -1925
  5. claude_mpm/agents/PM_INSTRUCTIONS.md +36 -9
  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/configure.py +620 -21
  10. claude_mpm/cli/commands/skills.py +166 -14
  11. claude_mpm/cli/executor.py +1 -0
  12. claude_mpm/cli/interactive/__init__.py +10 -0
  13. claude_mpm/cli/interactive/agent_wizard.py +30 -50
  14. claude_mpm/cli/interactive/questionary_styles.py +65 -0
  15. claude_mpm/cli/interactive/skill_selector.py +481 -0
  16. claude_mpm/cli/parsers/base_parser.py +5 -0
  17. claude_mpm/cli/startup.py +223 -388
  18. claude_mpm/constants.py +1 -0
  19. claude_mpm/core/claude_runner.py +2 -2
  20. claude_mpm/core/interactive_session.py +7 -7
  21. claude_mpm/core/output_style_manager.py +21 -13
  22. claude_mpm/core/unified_config.py +50 -8
  23. claude_mpm/core/unified_paths.py +30 -13
  24. claude_mpm/scripts/start_activity_logging.py +0 -0
  25. claude_mpm/services/agents/deployment/agent_template_builder.py +8 -0
  26. claude_mpm/services/agents/deployment/deployment_reconciler.py +577 -0
  27. claude_mpm/services/agents/deployment/startup_reconciliation.py +138 -0
  28. claude_mpm/services/agents/sources/git_source_sync_service.py +7 -4
  29. claude_mpm/services/agents/startup_sync.py +5 -2
  30. claude_mpm/services/pm_skills_deployer.py +4 -0
  31. claude_mpm/services/skills/git_skill_source_manager.py +24 -8
  32. claude_mpm/services/skills/selective_skill_deployer.py +82 -83
  33. claude_mpm/skills/bundled/collaboration/brainstorming/SKILL.md +79 -0
  34. claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/SKILL.md +178 -0
  35. claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/agent-prompts.md +577 -0
  36. claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/coordination-patterns.md +467 -0
  37. claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/examples.md +537 -0
  38. claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/troubleshooting.md +730 -0
  39. claude_mpm/skills/bundled/collaboration/git-worktrees.md +317 -0
  40. claude_mpm/skills/bundled/collaboration/requesting-code-review/SKILL.md +112 -0
  41. claude_mpm/skills/bundled/collaboration/requesting-code-review/references/code-reviewer-template.md +146 -0
  42. claude_mpm/skills/bundled/collaboration/requesting-code-review/references/review-examples.md +412 -0
  43. claude_mpm/skills/bundled/collaboration/stacked-prs.md +251 -0
  44. claude_mpm/skills/bundled/collaboration/writing-plans/SKILL.md +81 -0
  45. claude_mpm/skills/bundled/collaboration/writing-plans/references/best-practices.md +362 -0
  46. claude_mpm/skills/bundled/collaboration/writing-plans/references/plan-structure-templates.md +312 -0
  47. claude_mpm/skills/bundled/debugging/root-cause-tracing/SKILL.md +152 -0
  48. claude_mpm/skills/bundled/debugging/root-cause-tracing/references/advanced-techniques.md +668 -0
  49. claude_mpm/skills/bundled/debugging/root-cause-tracing/references/examples.md +587 -0
  50. claude_mpm/skills/bundled/debugging/root-cause-tracing/references/integration.md +438 -0
  51. claude_mpm/skills/bundled/debugging/root-cause-tracing/references/tracing-techniques.md +391 -0
  52. claude_mpm/skills/bundled/debugging/systematic-debugging/CREATION-LOG.md +119 -0
  53. claude_mpm/skills/bundled/debugging/systematic-debugging/SKILL.md +148 -0
  54. claude_mpm/skills/bundled/debugging/systematic-debugging/references/anti-patterns.md +483 -0
  55. claude_mpm/skills/bundled/debugging/systematic-debugging/references/examples.md +452 -0
  56. claude_mpm/skills/bundled/debugging/systematic-debugging/references/troubleshooting.md +449 -0
  57. claude_mpm/skills/bundled/debugging/systematic-debugging/references/workflow.md +411 -0
  58. claude_mpm/skills/bundled/debugging/systematic-debugging/test-academic.md +14 -0
  59. claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-1.md +58 -0
  60. claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-2.md +68 -0
  61. claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-3.md +69 -0
  62. claude_mpm/skills/bundled/debugging/verification-before-completion/SKILL.md +131 -0
  63. claude_mpm/skills/bundled/debugging/verification-before-completion/references/gate-function.md +325 -0
  64. claude_mpm/skills/bundled/debugging/verification-before-completion/references/integration-and-workflows.md +490 -0
  65. claude_mpm/skills/bundled/debugging/verification-before-completion/references/red-flags-and-failures.md +425 -0
  66. claude_mpm/skills/bundled/debugging/verification-before-completion/references/verification-patterns.md +499 -0
  67. claude_mpm/skills/bundled/infrastructure/env-manager/INTEGRATION.md +611 -0
  68. claude_mpm/skills/bundled/infrastructure/env-manager/README.md +596 -0
  69. claude_mpm/skills/bundled/infrastructure/env-manager/SKILL.md +260 -0
  70. claude_mpm/skills/bundled/infrastructure/env-manager/examples/nextjs-env-structure.md +315 -0
  71. claude_mpm/skills/bundled/infrastructure/env-manager/references/frameworks.md +436 -0
  72. claude_mpm/skills/bundled/infrastructure/env-manager/references/security.md +433 -0
  73. claude_mpm/skills/bundled/infrastructure/env-manager/references/synchronization.md +452 -0
  74. claude_mpm/skills/bundled/infrastructure/env-manager/references/troubleshooting.md +404 -0
  75. claude_mpm/skills/bundled/infrastructure/env-manager/references/validation.md +420 -0
  76. claude_mpm/skills/bundled/main/artifacts-builder/SKILL.md +86 -0
  77. claude_mpm/skills/bundled/main/internal-comms/SKILL.md +43 -0
  78. claude_mpm/skills/bundled/main/internal-comms/examples/3p-updates.md +47 -0
  79. claude_mpm/skills/bundled/main/internal-comms/examples/company-newsletter.md +65 -0
  80. claude_mpm/skills/bundled/main/internal-comms/examples/faq-answers.md +30 -0
  81. claude_mpm/skills/bundled/main/internal-comms/examples/general-comms.md +16 -0
  82. claude_mpm/skills/bundled/main/mcp-builder/SKILL.md +160 -0
  83. claude_mpm/skills/bundled/main/mcp-builder/reference/design_principles.md +412 -0
  84. claude_mpm/skills/bundled/main/mcp-builder/reference/evaluation.md +602 -0
  85. claude_mpm/skills/bundled/main/mcp-builder/reference/mcp_best_practices.md +915 -0
  86. claude_mpm/skills/bundled/main/mcp-builder/reference/node_mcp_server.md +916 -0
  87. claude_mpm/skills/bundled/main/mcp-builder/reference/python_mcp_server.md +752 -0
  88. claude_mpm/skills/bundled/main/mcp-builder/reference/workflow.md +1237 -0
  89. claude_mpm/skills/bundled/main/skill-creator/SKILL.md +189 -0
  90. claude_mpm/skills/bundled/main/skill-creator/references/best-practices.md +500 -0
  91. claude_mpm/skills/bundled/main/skill-creator/references/creation-workflow.md +464 -0
  92. claude_mpm/skills/bundled/main/skill-creator/references/examples.md +619 -0
  93. claude_mpm/skills/bundled/main/skill-creator/references/progressive-disclosure.md +437 -0
  94. claude_mpm/skills/bundled/main/skill-creator/references/skill-structure.md +231 -0
  95. claude_mpm/skills/bundled/php/espocrm-development/SKILL.md +170 -0
  96. claude_mpm/skills/bundled/php/espocrm-development/references/architecture.md +602 -0
  97. claude_mpm/skills/bundled/php/espocrm-development/references/common-tasks.md +821 -0
  98. claude_mpm/skills/bundled/php/espocrm-development/references/development-workflow.md +742 -0
  99. claude_mpm/skills/bundled/php/espocrm-development/references/frontend-customization.md +726 -0
  100. claude_mpm/skills/bundled/php/espocrm-development/references/hooks-and-services.md +764 -0
  101. claude_mpm/skills/bundled/php/espocrm-development/references/testing-debugging.md +831 -0
  102. claude_mpm/skills/bundled/pm/pm-bug-reporting/pm-bug-reporting.md +248 -0
  103. claude_mpm/skills/bundled/pm/pm-delegation-patterns/SKILL.md +167 -0
  104. claude_mpm/skills/bundled/pm/pm-git-file-tracking/SKILL.md +113 -0
  105. claude_mpm/skills/bundled/pm/pm-pr-workflow/SKILL.md +124 -0
  106. claude_mpm/skills/bundled/pm/pm-teaching-mode/SKILL.md +657 -0
  107. claude_mpm/skills/bundled/pm/pm-ticketing-integration/SKILL.md +154 -0
  108. claude_mpm/skills/bundled/pm/pm-verification-protocols/SKILL.md +198 -0
  109. claude_mpm/skills/bundled/react/flexlayout-react.md +742 -0
  110. claude_mpm/skills/bundled/rust/desktop-applications/SKILL.md +226 -0
  111. claude_mpm/skills/bundled/rust/desktop-applications/references/architecture-patterns.md +901 -0
  112. claude_mpm/skills/bundled/rust/desktop-applications/references/native-gui-frameworks.md +901 -0
  113. claude_mpm/skills/bundled/rust/desktop-applications/references/platform-integration.md +775 -0
  114. claude_mpm/skills/bundled/rust/desktop-applications/references/state-management.md +937 -0
  115. claude_mpm/skills/bundled/rust/desktop-applications/references/tauri-framework.md +770 -0
  116. claude_mpm/skills/bundled/rust/desktop-applications/references/testing-deployment.md +961 -0
  117. claude_mpm/skills/bundled/tauri/tauri-async-patterns.md +495 -0
  118. claude_mpm/skills/bundled/tauri/tauri-build-deploy.md +599 -0
  119. claude_mpm/skills/bundled/tauri/tauri-command-patterns.md +535 -0
  120. claude_mpm/skills/bundled/tauri/tauri-error-handling.md +613 -0
  121. claude_mpm/skills/bundled/tauri/tauri-event-system.md +648 -0
  122. claude_mpm/skills/bundled/tauri/tauri-file-system.md +673 -0
  123. claude_mpm/skills/bundled/tauri/tauri-frontend-integration.md +767 -0
  124. claude_mpm/skills/bundled/tauri/tauri-performance.md +669 -0
  125. claude_mpm/skills/bundled/tauri/tauri-state-management.md +573 -0
  126. claude_mpm/skills/bundled/tauri/tauri-testing.md +384 -0
  127. claude_mpm/skills/bundled/tauri/tauri-window-management.md +628 -0
  128. claude_mpm/skills/bundled/testing/condition-based-waiting/SKILL.md +119 -0
  129. claude_mpm/skills/bundled/testing/condition-based-waiting/references/patterns-and-implementation.md +253 -0
  130. claude_mpm/skills/bundled/testing/test-driven-development/SKILL.md +145 -0
  131. claude_mpm/skills/bundled/testing/test-driven-development/references/anti-patterns.md +543 -0
  132. claude_mpm/skills/bundled/testing/test-driven-development/references/examples.md +741 -0
  133. claude_mpm/skills/bundled/testing/test-driven-development/references/integration.md +470 -0
  134. claude_mpm/skills/bundled/testing/test-driven-development/references/philosophy.md +458 -0
  135. claude_mpm/skills/bundled/testing/test-driven-development/references/workflow.md +639 -0
  136. claude_mpm/skills/bundled/testing/test-quality-inspector/SKILL.md +458 -0
  137. claude_mpm/skills/bundled/testing/test-quality-inspector/examples/example-inspection-report.md +411 -0
  138. claude_mpm/skills/bundled/testing/test-quality-inspector/references/assertion-quality.md +317 -0
  139. claude_mpm/skills/bundled/testing/test-quality-inspector/references/inspection-checklist.md +270 -0
  140. claude_mpm/skills/bundled/testing/test-quality-inspector/references/red-flags.md +436 -0
  141. claude_mpm/skills/bundled/testing/testing-anti-patterns/SKILL.md +140 -0
  142. claude_mpm/skills/bundled/testing/testing-anti-patterns/references/completeness-anti-patterns.md +572 -0
  143. claude_mpm/skills/bundled/testing/testing-anti-patterns/references/core-anti-patterns.md +411 -0
  144. claude_mpm/skills/bundled/testing/testing-anti-patterns/references/detection-guide.md +569 -0
  145. claude_mpm/skills/bundled/testing/testing-anti-patterns/references/tdd-connection.md +695 -0
  146. claude_mpm/skills/bundled/testing/webapp-testing/SKILL.md +184 -0
  147. claude_mpm/skills/bundled/testing/webapp-testing/decision-tree.md +459 -0
  148. claude_mpm/skills/bundled/testing/webapp-testing/playwright-patterns.md +479 -0
  149. claude_mpm/skills/bundled/testing/webapp-testing/reconnaissance-pattern.md +687 -0
  150. claude_mpm/skills/bundled/testing/webapp-testing/server-management.md +758 -0
  151. claude_mpm/skills/bundled/testing/webapp-testing/troubleshooting.md +868 -0
  152. claude_mpm/utils/agent_dependency_loader.py +103 -4
  153. claude_mpm/utils/robust_installer.py +45 -24
  154. {claude_mpm-5.4.55.dist-info → claude_mpm-5.4.85.dist-info}/METADATA +47 -23
  155. {claude_mpm-5.4.55.dist-info → claude_mpm-5.4.85.dist-info}/RECORD +159 -47
  156. claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-311.pyc +0 -0
  157. claude_mpm/hooks/claude_hooks/__pycache__/correlation_manager.cpython-311.pyc +0 -0
  158. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-311.pyc +0 -0
  159. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-311.pyc +0 -0
  160. claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-311.pyc +0 -0
  161. claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-311.pyc +0 -0
  162. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-311.pyc +0 -0
  163. claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-311.pyc +0 -0
  164. claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-311.pyc +0 -0
  165. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-311.pyc +0 -0
  166. claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-311.pyc +0 -0
  167. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-311.pyc +0 -0
  168. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-311.pyc +0 -0
  169. {claude_mpm-5.4.55.dist-info → claude_mpm-5.4.85.dist-info}/WHEEL +0 -0
  170. {claude_mpm-5.4.55.dist-info → claude_mpm-5.4.85.dist-info}/entry_points.txt +0 -0
  171. {claude_mpm-5.4.55.dist-info → claude_mpm-5.4.85.dist-info}/licenses/LICENSE +0 -0
  172. {claude_mpm-5.4.55.dist-info → claude_mpm-5.4.85.dist-info}/licenses/LICENSE-FAQ.md +0 -0
  173. {claude_mpm-5.4.55.dist-info → claude_mpm-5.4.85.dist-info}/top_level.txt +0 -0
claude_mpm/cli/startup.py CHANGED
@@ -234,7 +234,7 @@ def deploy_bundled_skills():
234
234
  if not skills_config.get("auto_deploy", True):
235
235
  # Auto-deploy disabled, skip silently
236
236
  return
237
- except Exception:
237
+ except Exception: # nosec B110
238
238
  # If config loading fails, assume auto-deploy is enabled (default)
239
239
  pass
240
240
 
@@ -308,63 +308,60 @@ def deploy_output_style_on_startup():
308
308
  communication without emojis and exclamation points. Styles are project-specific
309
309
  to allow different projects to have different communication styles.
310
310
 
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.
311
+ DESIGN DECISION: This is non-blocking and idempotent. Deploys to user-level
312
+ directory (~/.claude/output-styles/) which is the official Claude Code location
313
+ for custom output styles.
314
314
 
315
- Deploys two styles:
316
- - claude-mpm-style.md (professional mode)
315
+ Deploys all styles:
316
+ - claude-mpm.md (professional mode)
317
317
  - claude-mpm-teacher.md (teaching mode)
318
+ - claude-mpm-founders.md (founders mode)
318
319
  """
319
320
  try:
320
- import shutil
321
- from pathlib import Path
321
+ from ..core.output_style_manager import OutputStyleManager
322
322
 
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"
323
+ # Initialize the output style manager
324
+ manager = OutputStyleManager()
327
325
 
328
- # Target directory (PROJECT-LEVEL, not user-level)
329
- project_dir = Path.cwd()
330
- output_styles_dir = project_dir / ".claude" / "settings" / "output-styles"
331
- professional_target = output_styles_dir / "claude-mpm-style.md"
332
- teacher_target = output_styles_dir / "claude-mpm-teacher.md"
333
-
334
- # Create directory if it doesn't exist
335
- output_styles_dir.mkdir(parents=True, exist_ok=True)
336
-
337
- # Check if already deployed (both files exist and have content)
338
- already_deployed = (
339
- professional_target.exists()
340
- and teacher_target.exists()
341
- and professional_target.stat().st_size > 0
342
- and teacher_target.stat().st_size > 0
343
- )
326
+ # Check if Claude Code version supports output styles (>= 1.0.83)
327
+ if not manager.supports_output_styles():
328
+ # Skip deployment for older versions
329
+ # The manager will fall back to injecting content directly
330
+ return
344
331
 
345
- if already_deployed:
332
+ # Check if all styles are already deployed and up-to-date
333
+ all_up_to_date = True
334
+ for style_config in manager.styles.values():
335
+ source_path = style_config["source"]
336
+ target_path = style_config["target"]
337
+
338
+ if not (
339
+ target_path.exists()
340
+ and source_path.exists()
341
+ and target_path.stat().st_size == source_path.stat().st_size
342
+ ):
343
+ all_up_to_date = False
344
+ break
345
+
346
+ if all_up_to_date:
346
347
  # Show feedback that output styles are ready
347
348
  print("✓ Output styles ready", flush=True)
348
349
  return
349
350
 
350
- # Deploy both styles
351
- deployed_count = 0
352
- if professional_source.exists():
353
- shutil.copy2(professional_source, professional_target)
354
- deployed_count += 1
351
+ # Deploy all styles using the manager
352
+ results = manager.deploy_all_styles(activate_default=True)
355
353
 
356
- if teacher_source.exists():
357
- shutil.copy2(teacher_source, teacher_target)
358
- deployed_count += 1
354
+ # Count successful deployments
355
+ deployed_count = sum(1 for success in results.values() if success)
359
356
 
360
357
  if deployed_count > 0:
361
358
  print(f"✓ Output styles deployed ({deployed_count} styles)", flush=True)
362
359
  else:
363
- # Source files missing - log but don't fail
360
+ # Deployment failed - log but don't fail startup
364
361
  from ..core.logger import get_logger
365
362
 
366
363
  logger = get_logger("cli")
367
- logger.debug("Output style source files not found")
364
+ logger.debug("Failed to deploy any output styles")
368
365
 
369
366
  except Exception as e:
370
367
  # Non-critical - log but don't fail startup
@@ -453,7 +450,7 @@ def _cleanup_orphaned_agents(deploy_target: Path, deployed_agents: list[str]) ->
453
450
  return removed_count
454
451
 
455
452
 
456
- def sync_remote_agents_on_startup():
453
+ def sync_remote_agents_on_startup(force_sync: bool = False):
457
454
  """
458
455
  Synchronize agent templates from remote sources on startup.
459
456
 
@@ -466,16 +463,16 @@ def sync_remote_agents_on_startup():
466
463
  block startup to ensure claude-mpm remains functional.
467
464
 
468
465
  Workflow:
469
- 1. Cleanup legacy agent cache directories (if any)
470
- 2. Sync all enabled Git sources (download/cache files) - Phase 1 progress bar
471
- 3. Deploy agents to ~/.claude/agents/ - Phase 2 progress bar
472
- 4. Cleanup orphaned agents (ours but no longer deployed) - Phase 3
466
+ 1. Sync all enabled Git sources (download/cache files) - Phase 1 progress bar
467
+ 2. Deploy agents to ~/.claude/agents/ - Phase 2 progress bar
468
+ 3. Cleanup orphaned agents (ours but no longer deployed) - Phase 3
469
+ 4. Cleanup legacy agent cache directories (after sync/deployment) - Phase 4
473
470
  5. Log deployment results
474
- """
475
- # Cleanup legacy cache directories first (before syncing)
476
- cleanup_legacy_agent_cache()
477
471
 
478
- # DEPRECATED: Legacy warning - replaced by automatic cleanup above
472
+ Args:
473
+ force_sync: Force download even if cache is fresh (bypasses ETag).
474
+ """
475
+ # DEPRECATED: Legacy warning - no-op function, kept for compatibility
479
476
  check_legacy_cache()
480
477
 
481
478
  try:
@@ -484,7 +481,6 @@ def sync_remote_agents_on_startup():
484
481
  from pathlib import Path
485
482
 
486
483
  from ..core.shared.config_loader import ConfigLoader
487
- from ..services.agents.deployment.agent_deployment import AgentDeploymentService
488
484
  from ..services.agents.startup_sync import sync_agents_on_startup
489
485
  from ..services.profile_manager import ProfileManager
490
486
  from ..utils.progress import ProgressBar
@@ -509,7 +505,7 @@ def sync_remote_agents_on_startup():
509
505
  )
510
506
 
511
507
  # Phase 1: Sync files from Git sources
512
- result = sync_agents_on_startup()
508
+ result = sync_agents_on_startup(force_refresh=force_sync)
513
509
 
514
510
  # Only proceed with deployment if sync was enabled and ran
515
511
  if result.get("enabled") and result.get("sources_synced", 0) > 0:
@@ -532,304 +528,89 @@ def sync_remote_agents_on_startup():
532
528
  logger.warning(f"Agent sync completed with {len(errors)} errors")
533
529
 
534
530
  # Phase 2: Deploy agents from cache to ~/.claude/agents/
535
- # This mirrors the skills deployment pattern (lines 371-407)
531
+ # Use reconciliation service to respect configuration.yaml settings
536
532
  try:
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)
586
-
587
- # Count agents in cache to show accurate progress
588
533
  from pathlib import Path
589
534
 
590
- cache_dir = Path.home() / ".claude-mpm" / "cache" / "agents"
591
- agent_count = 0
592
-
593
- if cache_dir.exists():
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
- )
535
+ from ..core.unified_config import UnifiedConfig
536
+ from ..services.agents.deployment.startup_reconciliation import (
537
+ perform_startup_reconciliation,
538
+ )
656
539
 
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)
663
- # Exclude PM templates, BASE-AGENT, and documentation files
664
- pm_templates = {
665
- "base-agent.md",
666
- "circuit_breakers.md",
667
- "pm_examples.md",
668
- "pm_red_flags.md",
669
- "research_gate_examples.md",
670
- "response_format.md",
671
- "ticket_completeness_examples.md",
672
- "validation_templates.md",
673
- "git_file_tracking.md",
674
- }
675
- # Documentation files to exclude (by filename)
676
- doc_files = {
677
- "readme.md",
678
- "changelog.md",
679
- "contributing.md",
680
- "implementation-summary.md",
681
- "reorganization-plan.md",
682
- "auto-deploy-index.md",
683
- }
684
-
685
- # Find all markdown files (after cleanup)
686
- all_md_files = list(cache_dir.rglob("*.md"))
687
-
688
- # Filter to only agent files:
689
- # 1. Must have "/agents/" in path (current structure supports
690
- # both flat and {owner}/{repo}/agents/ patterns)
691
- # 2. Must not be in PM templates or doc files
692
- # 3. Exclude BASE-AGENT.md which is not a deployable agent
693
- # 4. Exclude build artifacts (dist/, build/, .cache/)
694
- # to prevent double-counting
695
- agent_files = [
696
- f
697
- for f in all_md_files
698
- if (
699
- # Must be in an agent directory
700
- # Supports: cache/agents/{category}/... (flat)
701
- # Supports: cache/agents/{owner}/{repo}/agents/{category}/... (GitHub sync)
702
- "/agents/" in str(f)
703
- # Exclude PM templates, doc files, and BASE-AGENT
704
- and f.name.lower() not in pm_templates
705
- and f.name.lower() not in doc_files
706
- and f.name.lower() != "base-agent.md"
707
- # Exclude build artifacts (prevents double-counting
708
- # source + built files)
709
- and not any(
710
- part in str(f).split("/")
711
- for part in ["dist", "build", ".cache"]
712
- )
713
- )
714
- ]
715
- agent_count = len(agent_files)
716
-
717
- if agent_count > 0:
718
- # Deploy agents to project-level directory where Claude Code expects them
719
- deploy_target = Path.cwd() / ".claude" / "agents"
720
- deployment_result = deployment_service.deploy_agents(
721
- target_dir=deploy_target,
722
- force_rebuild=False, # Only deploy if versions differ
723
- deployment_mode="update", # Version-aware updates
724
- config=deploy_config, # Pass config to respect profile filtering
540
+ # Load configuration
541
+ unified_config = UnifiedConfig()
542
+
543
+ # Override with profile settings if active
544
+ if active_profile and profile_manager.active_profile:
545
+ # Get enabled agents from profile (returns Set[str])
546
+ profile_enabled_agents = (
547
+ profile_manager.active_profile.get_enabled_agents()
548
+ )
549
+ # Update config with profile's enabled list (convert Set to List)
550
+ unified_config.agents.enabled = list(profile_enabled_agents)
551
+ logger.info(
552
+ f"Profile '{active_profile}': Using {len(profile_enabled_agents)} enabled agents"
725
553
  )
726
554
 
727
- # Get actual counts from deployment result (reflects configured agents)
728
- deployed = len(deployment_result.get("deployed", []))
729
- updated = len(deployment_result.get("updated", []))
730
- skipped = len(deployment_result.get("skipped", []))
731
- total_configured = deployed + updated + skipped
732
-
733
- # FALLBACK: If deployment result doesn't track skipped agents (async path),
734
- # count existing agents in target directory as "already deployed"
735
- # This ensures accurate reporting when agents are already up-to-date
736
- if total_configured == 0 and deploy_target.exists():
737
- existing_agents = list(deploy_target.glob("*.md"))
738
- # Filter out non-agent files (e.g., README.md, INSTRUCTIONS.md)
739
- agent_count_in_target = len(
740
- [
741
- f
742
- for f in existing_agents
743
- if not f.name.startswith(("README", "INSTRUCTIONS"))
744
- ]
745
- )
746
- if agent_count_in_target > 0:
747
- # All agents already deployed - count them as skipped
748
- skipped = agent_count_in_target
749
- total_configured = agent_count_in_target
555
+ # Perform reconciliation to deploy configured agents
556
+ project_path = Path.cwd()
557
+ agent_result, _skill_result = perform_startup_reconciliation(
558
+ project_path=project_path, config=unified_config, silent=False
559
+ )
560
+
561
+ # Display results with progress bar
562
+ total_operations = (
563
+ len(agent_result.deployed)
564
+ + len(agent_result.removed)
565
+ + len(agent_result.unchanged)
566
+ )
750
567
 
751
- # Create progress bar with actual configured agent count (not raw file count)
568
+ if total_operations > 0:
752
569
  deploy_progress = ProgressBar(
753
- total=total_configured if total_configured > 0 else 1,
570
+ total=total_operations,
754
571
  prefix="Deploying agents",
755
572
  show_percentage=True,
756
573
  show_counter=True,
757
574
  )
758
-
759
- # Update progress bar to completion
760
- deploy_progress.update(
761
- total_configured if total_configured > 0 else 1
762
- )
763
-
764
- # Cleanup orphaned agents (ours but no longer deployed)
765
- # Get list of deployed agent filenames (what should remain)
766
- deployed_filenames = []
767
- for agent_name in deployment_result.get("deployed", []):
768
- deployed_filenames.append(f"{agent_name}.md")
769
- for agent_name in deployment_result.get("updated", []):
770
- deployed_filenames.append(f"{agent_name}.md")
771
- for agent_name in deployment_result.get("skipped", []):
772
- deployed_filenames.append(f"{agent_name}.md")
773
-
774
- # Run cleanup and get count of removed agents
775
- removed = _cleanup_orphaned_agents(
776
- deploy_target, deployed_filenames
575
+ deploy_progress.update(total_operations)
576
+
577
+ # Build summary message
578
+ deployed = len(agent_result.deployed)
579
+ removed = len(agent_result.removed)
580
+ unchanged = len(agent_result.unchanged)
581
+
582
+ summary_parts = []
583
+ if deployed > 0:
584
+ summary_parts.append(f"{deployed} new")
585
+ if removed > 0:
586
+ summary_parts.append(f"{removed} removed")
587
+ if unchanged > 0:
588
+ summary_parts.append(f"{unchanged} unchanged")
589
+
590
+ summary = f"Complete: {', '.join(summary_parts)}"
591
+ deploy_progress.finish(summary)
592
+
593
+ # Display errors if any
594
+ if agent_result.errors:
595
+ logger.warning(
596
+ f"Agent deployment completed with {len(agent_result.errors)} errors"
777
597
  )
598
+ print("\n⚠️ Agent Deployment Errors:")
599
+ max_errors_to_show = 10
600
+ errors_to_display = agent_result.errors[:max_errors_to_show]
778
601
 
779
- # Show total configured agents (deployed + updated + already existing)
780
- # Include cache count for context and removed count if any
781
- if deployed > 0 or updated > 0:
782
- if removed > 0:
783
- deploy_progress.finish(
784
- f"Complete: {deployed} new, {updated} updated, {skipped} unchanged, "
785
- f"{removed} removed ({total_configured} configured from {agent_count} files in cache)"
786
- )
787
- else:
788
- deploy_progress.finish(
789
- f"Complete: {deployed} new, {updated} updated, {skipped} unchanged "
790
- f"({total_configured} configured from {agent_count} files in cache)"
791
- )
792
- elif removed > 0:
793
- deploy_progress.finish(
794
- f"Complete: {total_configured} agents deployed, "
795
- f"{removed} removed ({agent_count} files in cache)"
796
- )
797
- else:
798
- deploy_progress.finish(
799
- f"Complete: {total_configured} agents deployed "
800
- f"({agent_count} files in cache)"
801
- )
602
+ for error in errors_to_display:
603
+ print(f" - {error}")
802
604
 
803
- # Display deployment errors to user (not just logs)
804
- deploy_errors = deployment_result.get("errors", [])
805
- if deploy_errors:
806
- # Log for debugging
807
- logger.warning(
808
- f"Agent deployment completed with {len(deploy_errors)} errors: {deploy_errors}"
809
- )
605
+ if len(agent_result.errors) > max_errors_to_show:
606
+ remaining = len(agent_result.errors) - max_errors_to_show
607
+ print(f" ... and {remaining} more error(s)")
810
608
 
811
- # Display errors to user with clear formatting
812
- print("\n⚠️ Agent Deployment Errors:")
813
-
814
- # Show first 10 errors to avoid overwhelming output
815
- max_errors_to_show = 10
816
- errors_to_display = deploy_errors[:max_errors_to_show]
817
-
818
- for error in errors_to_display:
819
- # Format error message for readability
820
- # Errors typically come as strings like "agent.md: Error message"
821
- print(f" - {error}")
822
-
823
- # If more errors exist, show count
824
- if len(deploy_errors) > max_errors_to_show:
825
- remaining = len(deploy_errors) - max_errors_to_show
826
- print(f" ... and {remaining} more error(s)")
827
-
828
- # Show summary message
829
- print(
830
- f"\n❌ Failed to deploy {len(deploy_errors)} agent(s). Please check the error messages above."
831
- )
832
- print(" Run with --verbose for detailed error information.\n")
609
+ print(
610
+ f"\n Failed to deploy {len(agent_result.errors)} agent(s). "
611
+ "Please check the error messages above."
612
+ )
613
+ print(" Run with --verbose for detailed error information.\n")
833
614
 
834
615
  except Exception as e:
835
616
  # Deployment failure shouldn't block startup
@@ -838,6 +619,11 @@ def sync_remote_agents_on_startup():
838
619
  logger = get_logger("cli")
839
620
  logger.warning(f"Failed to deploy agents from cache: {e}")
840
621
 
622
+ # Phase 4: Cleanup legacy agent cache directories (after sync/deployment)
623
+ # CRITICAL: This must run AFTER sync completes because sync may recreate
624
+ # legacy directories. Running cleanup here ensures they're removed.
625
+ cleanup_legacy_agent_cache()
626
+
841
627
  except Exception as e:
842
628
  # Non-critical - log but don't fail startup
843
629
  from ..core.logger import get_logger
@@ -846,8 +632,14 @@ def sync_remote_agents_on_startup():
846
632
  logger.debug(f"Failed to sync remote agents: {e}")
847
633
  # Continue execution - agent sync failure shouldn't block startup
848
634
 
635
+ # Cleanup legacy cache even if sync failed
636
+ try:
637
+ cleanup_legacy_agent_cache()
638
+ except Exception: # nosec B110
639
+ pass # Ignore cleanup errors
640
+
849
641
 
850
- def sync_remote_skills_on_startup():
642
+ def sync_remote_skills_on_startup(force_sync: bool = False):
851
643
  """
852
644
  Synchronize skill templates from remote sources on startup.
853
645
 
@@ -865,6 +657,9 @@ def sync_remote_skills_on_startup():
865
657
  4. Apply profile filtering if active
866
658
  5. Deploy resolved skills to ~/.claude/skills/ - Phase 2 progress bar
867
659
  6. Log deployment results with source indication
660
+
661
+ Args:
662
+ force_sync: Force download even if cache is fresh (bypasses ETag).
868
663
  """
869
664
  try:
870
665
  from pathlib import Path
@@ -970,7 +765,7 @@ def sync_remote_skills_on_startup():
970
765
 
971
766
  # Sync all sources with progress callback
972
767
  results = manager.sync_all_sources(
973
- force=False, progress_callback=sync_progress.update
768
+ force=force_sync, progress_callback=sync_progress.update
974
769
  )
975
770
 
976
771
  # Finish sync progress bar with clear breakdown
@@ -990,22 +785,37 @@ def sync_remote_skills_on_startup():
990
785
 
991
786
  # Phase 2: Scan agents and save to configuration.yaml
992
787
  # This step populates configuration.yaml with agent-referenced skills
993
- # BUGFIX: Removed `if results["synced_count"] > 0` condition to ensure
994
- # agent_referenced is always populated, even when using cached skills.
995
- # Previous behavior: If skills were cached, agent scan was skipped,
996
- # leaving agent_referenced: [] empty, which prevented cleanup.
788
+ # CRITICAL: Always scan agents to populate agent_referenced, even when using cached skills.
789
+ # Without this, skill_filter=None causes ALL skills to deploy and NO cleanup to run.
997
790
  agents_dir = Path.cwd() / ".claude" / "agents"
998
791
 
999
- # Scan agents for skill requirements (always run, not just on sync)
792
+ # Scan agents for skill requirements (ALWAYS run to ensure cleanup works)
1000
793
  agent_skills = get_required_skills_from_agents(agents_dir)
794
+ logger.info(
795
+ f"Agent scan found {len(agent_skills)} unique skills across deployed agents"
796
+ )
1001
797
 
1002
798
  # Save to project-level configuration.yaml
1003
799
  project_config_path = Path.cwd() / ".claude-mpm" / "configuration.yaml"
1004
800
  save_agent_skills_to_config(list(agent_skills), project_config_path)
801
+ logger.debug(
802
+ f"Saved {len(agent_skills)} agent-referenced skills to {project_config_path}"
803
+ )
1005
804
 
1006
805
  # Phase 3: Resolve which skills to deploy (user_defined or agent_referenced)
1007
806
  skills_to_deploy, skill_source = get_skills_to_deploy(project_config_path)
1008
807
 
808
+ # CRITICAL DEBUG: Log deployment resolution to diagnose cleanup issues
809
+ if skills_to_deploy:
810
+ logger.info(
811
+ f"Resolved {len(skills_to_deploy)} skills from {skill_source} (cleanup will run)"
812
+ )
813
+ else:
814
+ logger.warning(
815
+ f"No skills resolved from {skill_source} - will deploy ALL skills WITHOUT cleanup! "
816
+ f"This may indicate agent_referenced is empty in configuration.yaml."
817
+ )
818
+
1009
819
  # Phase 4: Apply profile filtering if active
1010
820
  if active_profile and profile_manager.active_profile:
1011
821
  # Filter skills based on profile
@@ -1053,9 +863,7 @@ def sync_remote_skills_on_startup():
1053
863
  total_skill_count = len(all_skills)
1054
864
 
1055
865
  # Determine skill count based on resolution
1056
- skill_count = (
1057
- len(skills_to_deploy) if skills_to_deploy else total_skill_count
1058
- )
866
+ skill_count = len(skills_to_deploy) if skills_to_deploy else total_skill_count
1059
867
 
1060
868
  if skill_count > 0:
1061
869
  # Deploy skills with resolved filter
@@ -1066,8 +874,13 @@ def sync_remote_skills_on_startup():
1066
874
  # Deploy to project-local directory with cleanup
1067
875
  deployment_result = manager.deploy_skills(
1068
876
  target_dir=Path.cwd() / ".claude" / "skills",
1069
- force=False,
1070
- skill_filter=set(skills_to_deploy) if skills_to_deploy else None,
877
+ force=force_sync,
878
+ # CRITICAL FIX: Empty list should mean "deploy no skills", not "deploy all"
879
+ # When skills_to_deploy is [], we want skill_filter=set() NOT skill_filter=None
880
+ # None means "no filtering" (deploy all), empty set means "filter to nothing"
881
+ skill_filter=set(skills_to_deploy)
882
+ if skills_to_deploy is not None
883
+ else None,
1071
884
  )
1072
885
 
1073
886
  # REMOVED: User-level deployment (lines 1068-1074)
@@ -1078,6 +891,7 @@ def sync_remote_skills_on_startup():
1078
891
  deployed = deployment_result.get("deployed_count", 0)
1079
892
  skipped = deployment_result.get("skipped_count", 0)
1080
893
  filtered = deployment_result.get("filtered_count", 0)
894
+ removed = deployment_result.get("removed_count", 0)
1081
895
  total_available = deployed + skipped
1082
896
 
1083
897
  # Only show progress bar if there are skills to deploy
@@ -1107,16 +921,25 @@ def sync_remote_skills_on_startup():
1107
921
  "user override" if skill_source == "user_defined" else "from agents"
1108
922
  )
1109
923
 
1110
- if deployed > 0:
924
+ # Build finish message with cleanup info
925
+ if deployed > 0 or removed > 0:
926
+ parts = []
927
+ if deployed > 0:
928
+ parts.append(f"{deployed} new")
929
+ if skipped > 0:
930
+ parts.append(f"{skipped} unchanged")
931
+ if removed > 0:
932
+ parts.append(f"{removed} removed")
933
+
934
+ status = ", ".join(parts)
935
+
1111
936
  if filtered > 0:
1112
937
  deploy_progress.finish(
1113
- f"Complete: {deployed} new, {skipped} unchanged "
1114
- f"({total_available} {source_label}, {filtered} files in cache)"
938
+ f"Complete: {status} ({total_available} {source_label}, {filtered} files in cache)"
1115
939
  )
1116
940
  else:
1117
941
  deploy_progress.finish(
1118
- f"Complete: {deployed} new, {skipped} unchanged "
1119
- f"({total_available} skills {source_label} from {total_skill_count} files in cache)"
942
+ f"Complete: {status} ({total_available} skills {source_label} from {total_skill_count} files in cache)"
1120
943
  )
1121
944
  elif filtered > 0:
1122
945
  # Skills filtered means agents require fewer skills than available
@@ -1124,10 +947,12 @@ def sync_remote_skills_on_startup():
1124
947
  f"No skills needed ({source_label}, {total_skill_count} files in cache)"
1125
948
  )
1126
949
  else:
1127
- deploy_progress.finish(
1128
- f"Complete: {total_available} skills {source_label} "
1129
- f"({total_skill_count} files in cache)"
1130
- )
950
+ # No changes - all skills already deployed
951
+ msg = f"Complete: {total_available} skills {source_label}"
952
+ if removed > 0:
953
+ msg += f", {removed} removed"
954
+ msg += f" ({total_skill_count} files in cache)"
955
+ deploy_progress.finish(msg)
1131
956
 
1132
957
  # Log deployment errors if any
1133
958
  from ..core.logger import get_logger
@@ -1244,61 +1069,64 @@ def show_skill_summary():
1244
1069
  Display skill availability summary on startup.
1245
1070
 
1246
1071
  WHY: Users should see at a glance how many skills are deployed and available
1247
- from collections, similar to the agent summary.
1072
+ from cache, similar to the agent summary showing "X deployed / Y cached".
1073
+
1074
+ DESIGN DECISION: Fast, non-blocking check that counts skills from:
1075
+ - Deployed skills: PROJECT-level .claude/skills/ directory
1076
+ - Cached skills: ~/.claude-mpm/cache/skills/ directory (from remote sources)
1248
1077
 
1249
- DESIGN DECISION: Fast, non-blocking check that counts skills from deployment
1250
- directory and collection repos. Shows "X installed (Y available)" format.
1078
+ Shows format: "✓ Skills: X deployed / Y cached"
1251
1079
  Failures are silent to avoid blocking startup.
1252
1080
  """
1253
1081
  try:
1254
1082
  from pathlib import Path
1255
1083
 
1256
- # Count deployed skills (installed)
1257
- skills_dir = Path.home() / ".claude" / "skills"
1258
- installed_count = 0
1259
- if skills_dir.exists():
1084
+ # Count deployed skills (PROJECT-level, not user-level)
1085
+ project_skills_dir = Path.cwd() / ".claude" / "skills"
1086
+ deployed_count = 0
1087
+ if project_skills_dir.exists():
1260
1088
  # Count directories with SKILL.md (excludes collection repos)
1261
1089
  # Exclude collection directories (obra-superpowers, etc.)
1262
1090
  skill_dirs = [
1263
1091
  d
1264
- for d in skills_dir.iterdir()
1092
+ for d in project_skills_dir.iterdir()
1265
1093
  if d.is_dir()
1266
1094
  and (d / "SKILL.md").exists()
1267
1095
  and not (d / ".git").exists() # Exclude collection repos
1268
1096
  ]
1269
- installed_count = len(skill_dirs)
1097
+ deployed_count = len(skill_dirs)
1270
1098
 
1271
- # Count available skills in collections
1272
- available_count = 0
1273
- if skills_dir.exists():
1274
- # Scan all collection directories (those with .git)
1275
- for collection_dir in skills_dir.iterdir():
1276
- if (
1277
- not collection_dir.is_dir()
1278
- or not (collection_dir / ".git").exists()
1279
- ):
1099
+ # Count cached skills (from remote sources, not deployed yet)
1100
+ # This matches the agent summary pattern: deployed vs cached
1101
+ cache_dir = Path.home() / ".claude-mpm" / "cache" / "skills"
1102
+ cached_count = 0
1103
+ if cache_dir.exists():
1104
+ # Scan all repository directories in cache
1105
+ # Cache structure: ~/.claude-mpm/cache/skills/{owner}/{repo}/...
1106
+ for repo_dir in cache_dir.rglob("*"):
1107
+ if not repo_dir.is_dir():
1280
1108
  continue
1281
1109
 
1282
- # Count skill directories in this collection
1110
+ # Count skill directories (those with SKILL.md)
1283
1111
  # Skills can be nested in: skills/category/skill-name/SKILL.md
1284
1112
  # or in flat structure: skill-name/SKILL.md
1285
- for root, dirs, files in os.walk(collection_dir):
1113
+ for root, dirs, files in os.walk(repo_dir):
1286
1114
  if "SKILL.md" in files:
1287
- # Exclude build artifacts and hidden directories (within the collection)
1288
- # Get relative path from collection_dir to avoid excluding based on .claude parent
1115
+ # Exclude build artifacts and hidden directories
1289
1116
  root_path = Path(root)
1290
- relative_parts = root_path.relative_to(collection_dir).parts
1291
1117
  if not any(
1292
1118
  part.startswith(".")
1293
1119
  or part in ["dist", "build", "__pycache__"]
1294
- for part in relative_parts
1120
+ for part in root_path.parts
1295
1121
  ):
1296
- available_count += 1
1122
+ cached_count += 1
1297
1123
 
1298
- # Display summary if we have skills
1299
- if installed_count > 0 or available_count > 0:
1124
+ # Display summary using agent summary format: "X deployed / Y cached"
1125
+ # Only show non-deployed cached skills (subtract deployed from cached)
1126
+ non_deployed_cached = max(0, cached_count - deployed_count)
1127
+ if deployed_count > 0 or non_deployed_cached > 0:
1300
1128
  print(
1301
- f"✓ Skills: {installed_count} installed ({available_count} available)",
1129
+ f"✓ Skills: {deployed_count} deployed / {non_deployed_cached} cached",
1302
1130
  flush=True,
1303
1131
  )
1304
1132
 
@@ -1372,7 +1200,7 @@ def auto_install_chrome_devtools_on_startup():
1372
1200
  if not chrome_devtools_config.get("auto_install", True):
1373
1201
  # Auto-install disabled, skip silently
1374
1202
  return
1375
- except Exception:
1203
+ except Exception: # nosec B110
1376
1204
  # If config loading fails, assume auto-install is enabled (default)
1377
1205
  pass
1378
1206
 
@@ -1390,7 +1218,7 @@ def auto_install_chrome_devtools_on_startup():
1390
1218
  # Continue execution - chrome-devtools installation failure shouldn't block startup
1391
1219
 
1392
1220
 
1393
- def run_background_services():
1221
+ def run_background_services(force_sync: bool = False):
1394
1222
  """
1395
1223
  Initialize all background services on startup.
1396
1224
 
@@ -1401,6 +1229,9 @@ def run_background_services():
1401
1229
  explicitly requests them via agent-manager commands. This prevents unwanted
1402
1230
  file creation in project .claude/ directories.
1403
1231
  See: SystemInstructionsDeployer and agent_deployment.py line 504-509
1232
+
1233
+ Args:
1234
+ force_sync: Force download even if cache is fresh (bypasses ETag).
1404
1235
  """
1405
1236
  # Sync hooks early to ensure up-to-date configuration
1406
1237
  # RATIONALE: Hooks should be synced before other services to fix stale configs
@@ -1411,7 +1242,9 @@ def run_background_services():
1411
1242
  check_mcp_auto_configuration()
1412
1243
  verify_mcp_gateway_startup()
1413
1244
  check_for_updates_async()
1414
- sync_remote_agents_on_startup() # Sync agents from remote sources
1245
+ sync_remote_agents_on_startup(
1246
+ force_sync=force_sync
1247
+ ) # Sync agents from remote sources
1415
1248
  show_agent_summary() # Display agent counts after deployment
1416
1249
 
1417
1250
  # Skills deployment order (precedence: remote > bundled)
@@ -1420,7 +1253,9 @@ def run_background_services():
1420
1253
  # 3. Discover and link runtime skills (user-added skills)
1421
1254
  # This ensures remote skills take precedence over bundled skills when names conflict
1422
1255
  deploy_bundled_skills() # Base layer: package-bundled skills
1423
- sync_remote_skills_on_startup() # Override layer: Git-based skills (takes precedence)
1256
+ sync_remote_skills_on_startup(
1257
+ force_sync=force_sync
1258
+ ) # Override layer: Git-based skills (takes precedence)
1424
1259
  discover_and_link_runtime_skills() # Discovery: user-added skills
1425
1260
  show_skill_summary() # Display skill counts after deployment
1426
1261
  verify_and_show_pm_skills() # PM skills verification and status
@@ -1620,7 +1455,7 @@ def verify_mcp_gateway_startup():
1620
1455
  loop.run_until_complete(
1621
1456
  asyncio.gather(*pending, return_exceptions=True)
1622
1457
  )
1623
- except Exception:
1458
+ except Exception: # nosec B110
1624
1459
  pass # Ignore cleanup errors
1625
1460
  finally:
1626
1461
  loop.close()
@@ -1714,7 +1549,7 @@ def check_for_updates_async():
1714
1549
 
1715
1550
  logger = get_logger("upgrade_check")
1716
1551
  logger.debug(f"Update check failed (non-critical): {e}")
1717
- except Exception:
1552
+ except Exception: # nosec B110
1718
1553
  pass # Avoid any errors in error handling
1719
1554
  finally:
1720
1555
  # Properly clean up event loop
@@ -1729,7 +1564,7 @@ def check_for_updates_async():
1729
1564
  loop.run_until_complete(
1730
1565
  asyncio.gather(*pending, return_exceptions=True)
1731
1566
  )
1732
- except Exception:
1567
+ except Exception: # nosec B110
1733
1568
  pass # Ignore cleanup errors
1734
1569
  finally:
1735
1570
  loop.close()