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