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.
- claude_mpm/VERSION +1 -1
- claude_mpm/__init__.py +4 -0
- claude_mpm/agents/CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md +1 -1
- claude_mpm/agents/PM_INSTRUCTIONS.md +166 -21
- claude_mpm/agents/agent_loader.py +3 -27
- claude_mpm/cli/__main__.py +4 -0
- claude_mpm/cli/chrome_devtools_installer.py +175 -0
- claude_mpm/cli/commands/agents.py +0 -31
- claude_mpm/cli/commands/auto_configure.py +210 -25
- claude_mpm/cli/commands/config.py +88 -2
- claude_mpm/cli/commands/configure.py +85 -43
- claude_mpm/cli/commands/configure_agent_display.py +3 -1
- claude_mpm/cli/commands/mpm_init/core.py +2 -45
- claude_mpm/cli/commands/skills.py +214 -189
- claude_mpm/cli/executor.py +3 -3
- claude_mpm/cli/parsers/agents_parser.py +0 -9
- claude_mpm/cli/parsers/auto_configure_parser.py +0 -138
- claude_mpm/cli/parsers/config_parser.py +153 -83
- claude_mpm/cli/parsers/skills_parser.py +3 -2
- claude_mpm/cli/startup.py +490 -41
- claude_mpm/commands/mpm-config.md +265 -0
- claude_mpm/commands/mpm-help.md +14 -95
- claude_mpm/commands/mpm-organize.md +350 -153
- claude_mpm/core/framework/formatters/content_formatter.py +3 -13
- claude_mpm/core/framework_loader.py +4 -2
- claude_mpm/core/logger.py +13 -0
- claude_mpm/hooks/claude_hooks/event_handlers.py +176 -76
- claude_mpm/hooks/claude_hooks/hook_handler.py +2 -0
- claude_mpm/hooks/claude_hooks/installer.py +33 -10
- claude_mpm/hooks/claude_hooks/memory_integration.py +26 -9
- claude_mpm/hooks/claude_hooks/response_tracking.py +2 -3
- claude_mpm/hooks/memory_integration_hook.py +46 -1
- claude_mpm/init.py +0 -19
- claude_mpm/scripts/claude-hook-handler.sh +58 -18
- claude_mpm/scripts/start_activity_logging.py +0 -0
- claude_mpm/services/agents/agent_recommendation_service.py +6 -7
- claude_mpm/services/agents/agent_review_service.py +280 -0
- claude_mpm/services/agents/deployment/agent_discovery_service.py +2 -3
- claude_mpm/services/agents/deployment/agent_template_builder.py +1 -0
- claude_mpm/services/agents/deployment/multi_source_deployment_service.py +78 -9
- claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +13 -0
- claude_mpm/services/agents/git_source_manager.py +14 -0
- claude_mpm/services/agents/loading/base_agent_manager.py +1 -13
- claude_mpm/services/agents/toolchain_detector.py +6 -3
- claude_mpm/services/command_deployment_service.py +81 -8
- claude_mpm/services/git/git_operations_service.py +93 -8
- claude_mpm/services/self_upgrade_service.py +120 -12
- claude_mpm/services/skills/__init__.py +3 -0
- claude_mpm/services/skills/git_skill_source_manager.py +32 -2
- claude_mpm/services/skills/selective_skill_deployer.py +704 -0
- claude_mpm/services/skills/skill_to_agent_mapper.py +406 -0
- claude_mpm/services/skills_deployer.py +126 -9
- {claude_mpm-5.4.3.dist-info → claude_mpm-5.4.21.dist-info}/METADATA +47 -8
- {claude_mpm-5.4.3.dist-info → claude_mpm-5.4.21.dist-info}/RECORD +58 -82
- {claude_mpm-5.4.3.dist-info → claude_mpm-5.4.21.dist-info}/entry_points.txt +0 -3
- claude_mpm-5.4.21.dist-info/licenses/LICENSE +94 -0
- claude_mpm-5.4.21.dist-info/licenses/LICENSE-FAQ.md +153 -0
- claude_mpm/agents/BASE_AGENT_TEMPLATE.md +0 -292
- claude_mpm/agents/BASE_DOCUMENTATION.md +0 -53
- claude_mpm/agents/BASE_ENGINEER.md +0 -658
- claude_mpm/agents/BASE_OPS.md +0 -219
- claude_mpm/agents/BASE_PM.md +0 -480
- claude_mpm/agents/BASE_PROMPT_ENGINEER.md +0 -787
- claude_mpm/agents/BASE_QA.md +0 -167
- claude_mpm/agents/BASE_RESEARCH.md +0 -53
- claude_mpm/agents/base_agent.json +0 -31
- claude_mpm/agents/base_agent_loader.py +0 -601
- claude_mpm/cli/commands/agents_detect.py +0 -380
- claude_mpm/cli/commands/agents_recommend.py +0 -309
- claude_mpm/cli/ticket_cli.py +0 -35
- claude_mpm/commands/mpm-agents-auto-configure.md +0 -278
- claude_mpm/commands/mpm-agents-detect.md +0 -177
- claude_mpm/commands/mpm-agents-list.md +0 -131
- claude_mpm/commands/mpm-agents-recommend.md +0 -223
- claude_mpm/commands/mpm-config-view.md +0 -150
- claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/correlation_manager.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-313.pyc +0 -0
- claude_mpm-5.4.3.dist-info/licenses/LICENSE +0 -21
- {claude_mpm-5.4.3.dist-info → claude_mpm-5.4.21.dist-info}/WHEEL +0 -0
- {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.
|
|
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
|
-
#
|
|
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
|
-
|
|
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
|
-
#
|
|
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: {
|
|
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: {
|
|
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.
|
|
507
|
-
3.
|
|
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
|
|
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}
|
|
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(
|
|
779
|
+
sync_progress.finish(
|
|
780
|
+
f"Complete: {downloaded} files downloaded ({total_skill_dirs} skills)"
|
|
781
|
+
)
|
|
586
782
|
|
|
587
|
-
# Phase 2:
|
|
588
|
-
# This
|
|
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
|
-
|
|
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
|
-
|
|
800
|
+
total_skill_count = len(all_skills)
|
|
594
801
|
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
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
|
-
|
|
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
|
-
|
|
813
|
+
skill_filter=set(skills_to_deploy) if skills_to_deploy else None,
|
|
610
814
|
)
|
|
611
815
|
|
|
612
|
-
#
|
|
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
|
-
#
|
|
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"
|
|
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
|
|
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
|
"""
|