claude-mpm 5.4.3__py3-none-any.whl → 5.4.21__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 (90) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/__init__.py +4 -0
  3. claude_mpm/agents/CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md +1 -1
  4. claude_mpm/agents/PM_INSTRUCTIONS.md +166 -21
  5. claude_mpm/agents/agent_loader.py +3 -27
  6. claude_mpm/cli/__main__.py +4 -0
  7. claude_mpm/cli/chrome_devtools_installer.py +175 -0
  8. claude_mpm/cli/commands/agents.py +0 -31
  9. claude_mpm/cli/commands/auto_configure.py +210 -25
  10. claude_mpm/cli/commands/config.py +88 -2
  11. claude_mpm/cli/commands/configure.py +85 -43
  12. claude_mpm/cli/commands/configure_agent_display.py +3 -1
  13. claude_mpm/cli/commands/mpm_init/core.py +2 -45
  14. claude_mpm/cli/commands/skills.py +214 -189
  15. claude_mpm/cli/executor.py +3 -3
  16. claude_mpm/cli/parsers/agents_parser.py +0 -9
  17. claude_mpm/cli/parsers/auto_configure_parser.py +0 -138
  18. claude_mpm/cli/parsers/config_parser.py +153 -83
  19. claude_mpm/cli/parsers/skills_parser.py +3 -2
  20. claude_mpm/cli/startup.py +490 -41
  21. claude_mpm/commands/mpm-config.md +265 -0
  22. claude_mpm/commands/mpm-help.md +14 -95
  23. claude_mpm/commands/mpm-organize.md +350 -153
  24. claude_mpm/core/framework/formatters/content_formatter.py +3 -13
  25. claude_mpm/core/framework_loader.py +4 -2
  26. claude_mpm/core/logger.py +13 -0
  27. claude_mpm/hooks/claude_hooks/event_handlers.py +176 -76
  28. claude_mpm/hooks/claude_hooks/hook_handler.py +2 -0
  29. claude_mpm/hooks/claude_hooks/installer.py +33 -10
  30. claude_mpm/hooks/claude_hooks/memory_integration.py +26 -9
  31. claude_mpm/hooks/claude_hooks/response_tracking.py +2 -3
  32. claude_mpm/hooks/memory_integration_hook.py +46 -1
  33. claude_mpm/init.py +0 -19
  34. claude_mpm/scripts/claude-hook-handler.sh +58 -18
  35. claude_mpm/scripts/start_activity_logging.py +0 -0
  36. claude_mpm/services/agents/agent_recommendation_service.py +6 -7
  37. claude_mpm/services/agents/agent_review_service.py +280 -0
  38. claude_mpm/services/agents/deployment/agent_discovery_service.py +2 -3
  39. claude_mpm/services/agents/deployment/agent_template_builder.py +1 -0
  40. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +78 -9
  41. claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +13 -0
  42. claude_mpm/services/agents/git_source_manager.py +14 -0
  43. claude_mpm/services/agents/loading/base_agent_manager.py +1 -13
  44. claude_mpm/services/agents/toolchain_detector.py +6 -3
  45. claude_mpm/services/command_deployment_service.py +81 -8
  46. claude_mpm/services/git/git_operations_service.py +93 -8
  47. claude_mpm/services/self_upgrade_service.py +120 -12
  48. claude_mpm/services/skills/__init__.py +3 -0
  49. claude_mpm/services/skills/git_skill_source_manager.py +32 -2
  50. claude_mpm/services/skills/selective_skill_deployer.py +704 -0
  51. claude_mpm/services/skills/skill_to_agent_mapper.py +406 -0
  52. claude_mpm/services/skills_deployer.py +126 -9
  53. {claude_mpm-5.4.3.dist-info → claude_mpm-5.4.21.dist-info}/METADATA +47 -8
  54. {claude_mpm-5.4.3.dist-info → claude_mpm-5.4.21.dist-info}/RECORD +58 -82
  55. {claude_mpm-5.4.3.dist-info → claude_mpm-5.4.21.dist-info}/entry_points.txt +0 -3
  56. claude_mpm-5.4.21.dist-info/licenses/LICENSE +94 -0
  57. claude_mpm-5.4.21.dist-info/licenses/LICENSE-FAQ.md +153 -0
  58. claude_mpm/agents/BASE_AGENT_TEMPLATE.md +0 -292
  59. claude_mpm/agents/BASE_DOCUMENTATION.md +0 -53
  60. claude_mpm/agents/BASE_ENGINEER.md +0 -658
  61. claude_mpm/agents/BASE_OPS.md +0 -219
  62. claude_mpm/agents/BASE_PM.md +0 -480
  63. claude_mpm/agents/BASE_PROMPT_ENGINEER.md +0 -787
  64. claude_mpm/agents/BASE_QA.md +0 -167
  65. claude_mpm/agents/BASE_RESEARCH.md +0 -53
  66. claude_mpm/agents/base_agent.json +0 -31
  67. claude_mpm/agents/base_agent_loader.py +0 -601
  68. claude_mpm/cli/commands/agents_detect.py +0 -380
  69. claude_mpm/cli/commands/agents_recommend.py +0 -309
  70. claude_mpm/cli/ticket_cli.py +0 -35
  71. claude_mpm/commands/mpm-agents-auto-configure.md +0 -278
  72. claude_mpm/commands/mpm-agents-detect.md +0 -177
  73. claude_mpm/commands/mpm-agents-list.md +0 -131
  74. claude_mpm/commands/mpm-agents-recommend.md +0 -223
  75. claude_mpm/commands/mpm-config-view.md +0 -150
  76. claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-313.pyc +0 -0
  77. claude_mpm/hooks/claude_hooks/__pycache__/correlation_manager.cpython-313.pyc +0 -0
  78. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-313.pyc +0 -0
  79. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-313.pyc +0 -0
  80. claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-313.pyc +0 -0
  81. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-313.pyc +0 -0
  82. claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-313.pyc +0 -0
  83. claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-313.pyc +0 -0
  84. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-313.pyc +0 -0
  85. claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-313.pyc +0 -0
  86. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-313.pyc +0 -0
  87. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-313.pyc +0 -0
  88. claude_mpm-5.4.3.dist-info/licenses/LICENSE +0 -21
  89. {claude_mpm-5.4.3.dist-info → claude_mpm-5.4.21.dist-info}/WHEEL +0 -0
  90. {claude_mpm-5.4.3.dist-info → claude_mpm-5.4.21.dist-info}/top_level.txt +0 -0
claude_mpm/cli/startup.py CHANGED
@@ -14,6 +14,52 @@ import warnings
14
14
  from pathlib import Path
15
15
 
16
16
 
17
+ def sync_hooks_on_startup(quiet: bool = False) -> bool:
18
+ """Ensure hooks are up-to-date on startup.
19
+
20
+ WHY: Users can have stale hook configurations in settings.json that cause errors.
21
+ Reinstalling hooks ensures the hook format matches the current code.
22
+
23
+ DESIGN DECISION: Shows brief status message on success for user awareness.
24
+ Failures are logged but don't prevent startup to ensure claude-mpm remains functional.
25
+
26
+ Args:
27
+ quiet: If True, suppress all output (used internally)
28
+
29
+ Returns:
30
+ bool: True if hooks were synced successfully, False otherwise
31
+ """
32
+ try:
33
+ from ..hooks.claude_hooks.installer import HookInstaller
34
+
35
+ installer = HookInstaller()
36
+
37
+ # Show brief status (hooks sync is fast)
38
+ if not quiet:
39
+ print("Syncing Claude Code hooks...", end=" ", flush=True)
40
+
41
+ # Reinstall hooks (force=True ensures update)
42
+ success = installer.install_hooks(force=True)
43
+
44
+ if not quiet:
45
+ if success:
46
+ print("✓")
47
+ else:
48
+ print("(skipped)")
49
+
50
+ return success
51
+
52
+ except Exception as e:
53
+ if not quiet:
54
+ print("(error)")
55
+ # Log but don't fail startup
56
+ from ..core.logger import get_logger
57
+
58
+ logger = get_logger("startup")
59
+ logger.warning(f"Hook sync failed (non-fatal): {e}")
60
+ return False
61
+
62
+
17
63
  def check_legacy_cache() -> None:
18
64
  """Check for legacy cache/agents/ directory and warn user.
19
65
 
@@ -294,6 +340,84 @@ def deploy_output_style_on_startup():
294
340
  # Continue execution - output style deployment shouldn't block startup
295
341
 
296
342
 
343
+ def _cleanup_orphaned_agents(deploy_target: Path, deployed_agents: list[str]) -> int:
344
+ """Remove agents that are managed by claude-mpm but no longer deployed.
345
+
346
+ WHY: When agent configurations change, old agents should be removed to avoid
347
+ confusion and stale agent references. Only removes claude-mpm managed agents,
348
+ leaving user-created agents untouched.
349
+
350
+ SAFETY: Only removes files with claude-mpm ownership markers in frontmatter.
351
+ Files without frontmatter or without ownership indicators are preserved.
352
+
353
+ Args:
354
+ deploy_target: Path to .claude/agents/ directory
355
+ deployed_agents: List of agent filenames that should remain
356
+
357
+ Returns:
358
+ Number of agents removed
359
+ """
360
+ import re
361
+
362
+ import yaml
363
+
364
+ from ..core.logger import get_logger
365
+
366
+ logger = get_logger("cli")
367
+ removed_count = 0
368
+ deployed_set = set(deployed_agents)
369
+
370
+ if not deploy_target.exists():
371
+ return 0
372
+
373
+ # Scan all .md files in agents directory
374
+ for agent_file in deploy_target.glob("*.md"):
375
+ # Skip hidden files
376
+ if agent_file.name.startswith("."):
377
+ continue
378
+
379
+ # Skip if this agent should remain deployed
380
+ if agent_file.name in deployed_set:
381
+ continue
382
+
383
+ # Check if this is a claude-mpm managed agent
384
+ try:
385
+ content = agent_file.read_text(encoding="utf-8")
386
+
387
+ # Parse YAML frontmatter
388
+ if content.startswith("---"):
389
+ match = re.match(r"^---\n(.*?)\n---", content, re.DOTALL)
390
+ if match:
391
+ frontmatter = yaml.safe_load(match.group(1))
392
+
393
+ # Check ownership indicators
394
+ is_ours = False
395
+ if frontmatter:
396
+ author = frontmatter.get("author", "")
397
+ source = frontmatter.get("source", "")
398
+ agent_id = frontmatter.get("agent_id", "")
399
+
400
+ # It's ours if it has any of these markers
401
+ if (
402
+ "Claude MPM" in str(author)
403
+ or source == "remote"
404
+ or agent_id
405
+ ):
406
+ is_ours = True
407
+
408
+ if is_ours:
409
+ # Safe to remove - it's our agent but not deployed
410
+ agent_file.unlink()
411
+ removed_count += 1
412
+ logger.info(f"Removed orphaned agent: {agent_file.name}")
413
+
414
+ except Exception as e:
415
+ logger.debug(f"Could not check agent {agent_file.name}: {e}")
416
+ # Don't remove if we can't verify ownership
417
+
418
+ return removed_count
419
+
420
+
297
421
  def sync_remote_agents_on_startup():
298
422
  """
299
423
  Synchronize agent templates from remote sources on startup.
@@ -309,7 +433,8 @@ def sync_remote_agents_on_startup():
309
433
  Workflow:
310
434
  1. Sync all enabled Git sources (download/cache files) - Phase 1 progress bar
311
435
  2. Deploy agents to ~/.claude/agents/ - Phase 2 progress bar
312
- 3. Log deployment results
436
+ 3. Cleanup orphaned agents (ours but no longer deployed) - Phase 3
437
+ 4. Log deployment results
313
438
  """
314
439
  # Check for legacy cache and warn user if found
315
440
  check_legacy_cache()
@@ -408,14 +533,6 @@ def sync_remote_agents_on_startup():
408
533
  agent_count = len(agent_files)
409
534
 
410
535
  if agent_count > 0:
411
- # Create progress bar for deployment phase
412
- deploy_progress = ProgressBar(
413
- total=agent_count,
414
- prefix="Deploying agents",
415
- show_percentage=True,
416
- show_counter=True,
417
- )
418
-
419
536
  # Deploy agents to project-level directory where Claude Code expects them
420
537
  deploy_target = Path.cwd() / ".claude" / "agents"
421
538
  deployment_result = deployment_service.deploy_agents(
@@ -424,23 +541,80 @@ def sync_remote_agents_on_startup():
424
541
  deployment_mode="update", # Version-aware updates
425
542
  )
426
543
 
427
- # Update progress bar (single increment since deploy_agents is batch)
428
- deploy_progress.update(agent_count)
429
-
430
- # Finish deployment progress bar
544
+ # Get actual counts from deployment result (reflects configured agents)
431
545
  deployed = len(deployment_result.get("deployed", []))
432
546
  updated = len(deployment_result.get("updated", []))
433
547
  skipped = len(deployment_result.get("skipped", []))
434
- total_available = deployed + updated + skipped
548
+ total_configured = deployed + updated + skipped
549
+
550
+ # FALLBACK: If deployment result doesn't track skipped agents (async path),
551
+ # count existing agents in target directory as "already deployed"
552
+ # This ensures accurate reporting when agents are already up-to-date
553
+ if total_configured == 0 and deploy_target.exists():
554
+ existing_agents = list(deploy_target.glob("*.md"))
555
+ # Filter out non-agent files (e.g., README.md, INSTRUCTIONS.md)
556
+ agent_count_in_target = len(
557
+ [
558
+ f
559
+ for f in existing_agents
560
+ if not f.name.startswith(("README", "INSTRUCTIONS"))
561
+ ]
562
+ )
563
+ if agent_count_in_target > 0:
564
+ # All agents already deployed - count them as skipped
565
+ skipped = agent_count_in_target
566
+ total_configured = agent_count_in_target
567
+
568
+ # Create progress bar with actual configured agent count (not raw file count)
569
+ deploy_progress = ProgressBar(
570
+ total=total_configured if total_configured > 0 else 1,
571
+ prefix="Deploying agents",
572
+ show_percentage=True,
573
+ show_counter=True,
574
+ )
435
575
 
436
- # Show total available agents (deployed + updated + already existing)
576
+ # Update progress bar to completion
577
+ deploy_progress.update(
578
+ total_configured if total_configured > 0 else 1
579
+ )
580
+
581
+ # Cleanup orphaned agents (ours but no longer deployed)
582
+ # Get list of deployed agent filenames (what should remain)
583
+ deployed_filenames = []
584
+ for agent_name in deployment_result.get("deployed", []):
585
+ deployed_filenames.append(f"{agent_name}.md")
586
+ for agent_name in deployment_result.get("updated", []):
587
+ deployed_filenames.append(f"{agent_name}.md")
588
+ for agent_name in deployment_result.get("skipped", []):
589
+ deployed_filenames.append(f"{agent_name}.md")
590
+
591
+ # Run cleanup and get count of removed agents
592
+ removed = _cleanup_orphaned_agents(
593
+ deploy_target, deployed_filenames
594
+ )
595
+
596
+ # Show total configured agents (deployed + updated + already existing)
597
+ # Include repo count for context and removed count if any
437
598
  if deployed > 0 or updated > 0:
599
+ if removed > 0:
600
+ deploy_progress.finish(
601
+ f"Complete: {deployed} new, {updated} updated, {skipped} unchanged, "
602
+ f"{removed} removed ({total_configured} configured from {agent_count} in repo)"
603
+ )
604
+ else:
605
+ deploy_progress.finish(
606
+ f"Complete: {deployed} new, {updated} updated, {skipped} unchanged "
607
+ f"({total_configured} configured from {agent_count} in repo)"
608
+ )
609
+ elif removed > 0:
438
610
  deploy_progress.finish(
439
- f"Complete: {deployed} deployed, {updated} updated, {skipped} already present ({total_available} total)"
611
+ f"Complete: {total_configured} agents ready - all unchanged, "
612
+ f"{removed} removed ({agent_count} available in repo)"
440
613
  )
441
614
  else:
442
615
  deploy_progress.finish(
443
- f"Complete: {total_available} agents ready (all up-to-date)"
616
+ f"Complete: {total_configured} agents ready - all unchanged "
617
+ f"({agent_count} available in repo)"
444
618
  )
445
619
 
446
620
  # Display deployment errors to user (not just logs)
@@ -503,14 +677,21 @@ def sync_remote_skills_on_startup():
503
677
 
504
678
  Workflow:
505
679
  1. Sync all enabled Git sources (download/cache files) - Phase 1 progress bar
506
- 2. Deploy skills to ~/.claude/skills/ with flat structure - Phase 2 progress bar
507
- 3. Log deployment results
680
+ 2. Scan deployed agents for skill requirements save to configuration.yaml
681
+ 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
508
684
  """
509
685
  try:
510
686
  from pathlib import Path
511
687
 
512
688
  from ..config.skill_sources import SkillSourceConfiguration
513
689
  from ..services.skills.git_skill_source_manager import GitSkillSourceManager
690
+ from ..services.skills.selective_skill_deployer import (
691
+ get_required_skills_from_agents,
692
+ get_skills_to_deploy,
693
+ save_agent_skills_to_config,
694
+ )
514
695
  from ..utils.progress import ProgressBar
515
696
 
516
697
  config = SkillSourceConfiguration()
@@ -530,6 +711,8 @@ def sync_remote_skills_on_startup():
530
711
 
531
712
  # Discover total file count across all sources
532
713
  total_file_count = 0
714
+ total_skill_dirs = 0 # Count actual skill directories (folders with SKILL.md)
715
+
533
716
  for source in enabled_sources:
534
717
  try:
535
718
  # Parse GitHub URL
@@ -553,15 +736,26 @@ def sync_remote_skills_on_startup():
553
736
  ]
554
737
  total_file_count += len(relevant_files)
555
738
 
739
+ # Count skill directories (unique directories containing SKILL.md)
740
+ skill_dirs = set()
741
+ for f in all_files:
742
+ if f.endswith("/SKILL.md"):
743
+ # Extract directory path
744
+ skill_dir = "/".join(f.split("/")[:-1])
745
+ skill_dirs.add(skill_dir)
746
+ total_skill_dirs += len(skill_dirs)
747
+
556
748
  except Exception as e:
557
749
  logger.debug(f"Failed to discover files for {source.id}: {e}")
558
750
  # Use estimate if discovery fails
559
751
  total_file_count += 150
752
+ total_skill_dirs += 50 # Estimate ~50 skills
560
753
 
561
754
  # Create progress bar for sync phase with actual file count
755
+ # Note: We sync files (md, json, etc.), but will deploy skill directories
562
756
  sync_progress = ProgressBar(
563
757
  total=total_file_count if total_file_count > 0 else 1,
564
- prefix="Syncing skills",
758
+ prefix="Syncing skill files",
565
759
  show_percentage=True,
566
760
  show_counter=True,
567
761
  )
@@ -578,51 +772,100 @@ def sync_remote_skills_on_startup():
578
772
 
579
773
  if cached > 0:
580
774
  sync_progress.finish(
581
- f"Complete: {downloaded} downloaded, {cached} cached ({total_files} total)"
775
+ f"Complete: {downloaded} downloaded, {cached} cached ({total_files} files, {total_skill_dirs} skills)"
582
776
  )
583
777
  else:
584
778
  # All new downloads (first sync)
585
- sync_progress.finish(f"Complete: {downloaded} files downloaded")
779
+ sync_progress.finish(
780
+ f"Complete: {downloaded} files downloaded ({total_skill_dirs} skills)"
781
+ )
586
782
 
587
- # Phase 2: Deploy skills to ~/.claude/skills/
588
- # This flattens nested Git structure (e.g., collaboration/parallel-agents/SKILL.md)
589
- # into flat deployment (e.g., collaboration-dispatching-parallel-agents/SKILL.md)
783
+ # Phase 2: Scan agents and save to configuration.yaml
784
+ # This step populates configuration.yaml with agent-referenced skills
590
785
  if results["synced_count"] > 0:
591
- # Get all skills to determine deployment count
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)
794
+
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)
797
+
798
+ # Get all skills to determine counts
592
799
  all_skills = manager.get_all_skills()
593
- skill_count = len(all_skills)
800
+ total_skill_count = len(all_skills)
594
801
 
595
- if skill_count > 0:
596
- # Create progress bar for deployment phase
597
- deploy_progress = ProgressBar(
598
- total=skill_count,
599
- prefix="Deploying skill directories",
600
- show_percentage=True,
601
- show_counter=True,
602
- )
802
+ # Determine skill count based on resolution
803
+ skill_count = (
804
+ len(skills_to_deploy) if skills_to_deploy else total_skill_count
805
+ )
603
806
 
604
- # Deploy skills with progress callback
807
+ if skill_count > 0:
808
+ # Deploy skills with resolved filter
605
809
  # Deploy to project directory (like agents), not user directory
606
810
  deployment_result = manager.deploy_skills(
607
811
  target_dir=Path.cwd() / ".claude" / "skills",
608
812
  force=False,
609
- progress_callback=deploy_progress.update,
813
+ skill_filter=set(skills_to_deploy) if skills_to_deploy else None,
610
814
  )
611
815
 
612
- # Finish deployment progress bar
816
+ # Get actual counts from deployment result
613
817
  deployed = deployment_result.get("deployed_count", 0)
614
818
  skipped = deployment_result.get("skipped_count", 0)
819
+ filtered = deployment_result.get("filtered_count", 0)
615
820
  total_available = deployed + skipped
616
821
 
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)
841
+
617
842
  # Show total available skills (deployed + already existing)
618
- # This is more user-friendly than just showing newly deployed count
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"
847
+ )
848
+
619
849
  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
620
862
  deploy_progress.finish(
621
- f"Complete: {deployed} deployed, {skipped} already present ({total_available} total)"
863
+ f"No skills needed ({source_label}, {total_skill_count} available in repo)"
622
864
  )
623
865
  else:
624
866
  deploy_progress.finish(
625
- f"Complete: {total_available} skills ready (all up-to-date)"
867
+ f"Complete: {total_available} skills {source_label} "
868
+ f"({total_skill_count} available in repo)"
626
869
  )
627
870
 
628
871
  # Log deployment errors if any
@@ -651,6 +894,202 @@ def sync_remote_skills_on_startup():
651
894
  # Continue execution - skill sync failure shouldn't block startup
652
895
 
653
896
 
897
+ def show_agent_summary():
898
+ """
899
+ Display agent availability summary on startup.
900
+
901
+ WHY: Users should see at a glance how many agents are available and installed
902
+ without having to run /mpm-agents list.
903
+
904
+ DESIGN DECISION: Fast, non-blocking check that counts agents from the deployment
905
+ directory. Shows simple "X installed / Y available" format. Failures are silent
906
+ to avoid blocking startup.
907
+ """
908
+ try:
909
+ from pathlib import Path
910
+
911
+ # Count deployed agents (installed)
912
+ deploy_target = Path.cwd() / ".claude" / "agents"
913
+ installed_count = 0
914
+ if deploy_target.exists():
915
+ # Count .md files, excluding README and other docs
916
+ agent_files = [
917
+ f
918
+ for f in deploy_target.glob("*.md")
919
+ if not f.name.startswith(("README", "INSTRUCTIONS", "."))
920
+ ]
921
+ installed_count = len(agent_files)
922
+
923
+ # Count available agents in cache (from remote sources)
924
+ cache_dir = Path.home() / ".claude-mpm" / "cache" / "remote-agents"
925
+ available_count = 0
926
+ if cache_dir.exists():
927
+ # Use same filtering logic as agent deployment (lines 486-533 in startup.py)
928
+ pm_templates = {
929
+ "base-agent.md",
930
+ "circuit_breakers.md",
931
+ "pm_examples.md",
932
+ "pm_red_flags.md",
933
+ "research_gate_examples.md",
934
+ "response_format.md",
935
+ "ticket_completeness_examples.md",
936
+ "validation_templates.md",
937
+ "git_file_tracking.md",
938
+ }
939
+ doc_files = {
940
+ "readme.md",
941
+ "changelog.md",
942
+ "contributing.md",
943
+ "implementation-summary.md",
944
+ "reorganization-plan.md",
945
+ "auto-deploy-index.md",
946
+ }
947
+
948
+ # Find all markdown files in agents/ directories
949
+ all_md_files = list(cache_dir.rglob("*.md"))
950
+ agent_files = [
951
+ f
952
+ for f in all_md_files
953
+ if (
954
+ "/agents/" in str(f)
955
+ and f.name.lower() not in pm_templates
956
+ and f.name.lower() not in doc_files
957
+ and f.name.lower() != "base-agent.md"
958
+ and not any(
959
+ part in str(f).split("/")
960
+ for part in ["dist", "build", ".cache"]
961
+ )
962
+ )
963
+ ]
964
+ available_count = len(agent_files)
965
+
966
+ # Display summary if we have agents
967
+ if installed_count > 0 or available_count > 0:
968
+ print(
969
+ f"✓ Agents: {installed_count} installed / {available_count} available",
970
+ flush=True,
971
+ )
972
+
973
+ except Exception as e:
974
+ # Silent failure - agent summary is informational only
975
+ from ..core.logger import get_logger
976
+
977
+ logger = get_logger("cli")
978
+ logger.debug(f"Failed to generate agent summary: {e}")
979
+
980
+
981
+ def show_skill_summary():
982
+ """
983
+ Display skill availability summary on startup.
984
+
985
+ WHY: Users should see at a glance how many skills are deployed and available
986
+ from collections, similar to the agent summary.
987
+
988
+ DESIGN DECISION: Fast, non-blocking check that counts skills from deployment
989
+ directory and collection repos. Shows "X installed (Y available)" format.
990
+ Failures are silent to avoid blocking startup.
991
+ """
992
+ try:
993
+ from pathlib import Path
994
+
995
+ # Count deployed skills (installed)
996
+ skills_dir = Path.home() / ".claude" / "skills"
997
+ installed_count = 0
998
+ if skills_dir.exists():
999
+ # Count directories with SKILL.md (excludes collection repos)
1000
+ # Exclude collection directories (obra-superpowers, etc.)
1001
+ skill_dirs = [
1002
+ d
1003
+ for d in skills_dir.iterdir()
1004
+ if d.is_dir()
1005
+ and (d / "SKILL.md").exists()
1006
+ and not (d / ".git").exists() # Exclude collection repos
1007
+ ]
1008
+ installed_count = len(skill_dirs)
1009
+
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
+ ):
1019
+ continue
1020
+
1021
+ # Count skill directories in this collection
1022
+ # Skills can be nested in: skills/category/skill-name/SKILL.md
1023
+ # or in flat structure: skill-name/SKILL.md
1024
+ for root, dirs, files in os.walk(collection_dir):
1025
+ 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
1028
+ root_path = Path(root)
1029
+ relative_parts = root_path.relative_to(collection_dir).parts
1030
+ if not any(
1031
+ part.startswith(".")
1032
+ or part in ["dist", "build", "__pycache__"]
1033
+ for part in relative_parts
1034
+ ):
1035
+ available_count += 1
1036
+
1037
+ # Display summary if we have skills
1038
+ if installed_count > 0 or available_count > 0:
1039
+ print(
1040
+ f"✓ Skills: {installed_count} installed ({available_count} available)",
1041
+ flush=True,
1042
+ )
1043
+
1044
+ except Exception as e:
1045
+ # Silent failure - skill summary is informational only
1046
+ from ..core.logger import get_logger
1047
+
1048
+ logger = get_logger("cli")
1049
+ logger.debug(f"Failed to generate skill summary: {e}")
1050
+
1051
+
1052
+ def auto_install_chrome_devtools_on_startup():
1053
+ """
1054
+ Automatically install chrome-devtools-mcp on startup if enabled.
1055
+
1056
+ WHY: Browser automation capabilities should be available out-of-the-box without
1057
+ manual MCP server configuration. chrome-devtools-mcp provides powerful browser
1058
+ interaction tools for Claude Code.
1059
+
1060
+ DESIGN DECISION: Non-blocking installation that doesn't prevent startup if it fails.
1061
+ Respects user configuration setting (enabled by default). Only installs if not
1062
+ already configured in Claude.
1063
+ """
1064
+ try:
1065
+ # Check if auto-install is disabled in config
1066
+ from ..config.config_loader import ConfigLoader
1067
+
1068
+ config_loader = ConfigLoader()
1069
+ try:
1070
+ config = config_loader.load_main_config()
1071
+ chrome_devtools_config = config.get("chrome_devtools", {})
1072
+ if not chrome_devtools_config.get("auto_install", True):
1073
+ # Auto-install disabled, skip silently
1074
+ return
1075
+ except Exception:
1076
+ # If config loading fails, assume auto-install is enabled (default)
1077
+ pass
1078
+
1079
+ # Import and run chrome-devtools installation
1080
+ from ..cli.chrome_devtools_installer import auto_install_chrome_devtools
1081
+
1082
+ auto_install_chrome_devtools(quiet=False)
1083
+
1084
+ except Exception as e:
1085
+ # Import logger here to avoid circular imports
1086
+ from ..core.logger import get_logger
1087
+
1088
+ logger = get_logger("cli")
1089
+ logger.debug(f"Failed to auto-install chrome-devtools-mcp: {e}")
1090
+ # Continue execution - chrome-devtools installation failure shouldn't block startup
1091
+
1092
+
654
1093
  def run_background_services():
655
1094
  """
656
1095
  Initialize all background services on startup.
@@ -663,11 +1102,17 @@ def run_background_services():
663
1102
  file creation in project .claude/ directories.
664
1103
  See: SystemInstructionsDeployer and agent_deployment.py line 504-509
665
1104
  """
1105
+ # Sync hooks early to ensure up-to-date configuration
1106
+ # RATIONALE: Hooks should be synced before other services to fix stale configs
1107
+ # This is fast (<100ms) and non-blocking, so it doesn't delay startup
1108
+ sync_hooks_on_startup() # Shows "Syncing Claude Code hooks... ✓"
1109
+
666
1110
  initialize_project_registry()
667
1111
  check_mcp_auto_configuration()
668
1112
  verify_mcp_gateway_startup()
669
1113
  check_for_updates_async()
670
1114
  sync_remote_agents_on_startup() # Sync agents from remote sources
1115
+ show_agent_summary() # Display agent counts after deployment
671
1116
 
672
1117
  # Skills deployment order (precedence: remote > bundled)
673
1118
  # 1. Deploy bundled skills first (base layer from package)
@@ -677,9 +1122,13 @@ def run_background_services():
677
1122
  deploy_bundled_skills() # Base layer: package-bundled skills
678
1123
  sync_remote_skills_on_startup() # Override layer: Git-based skills (takes precedence)
679
1124
  discover_and_link_runtime_skills() # Discovery: user-added skills
1125
+ show_skill_summary() # Display skill counts after deployment
680
1126
 
681
1127
  deploy_output_style_on_startup()
682
1128
 
1129
+ # Auto-install chrome-devtools-mcp for browser automation
1130
+ auto_install_chrome_devtools_on_startup()
1131
+
683
1132
 
684
1133
  def setup_mcp_server_logging(args):
685
1134
  """