claude-mpm 5.1.9__py3-none-any.whl → 5.4.48__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/BASE_AGENT.md +164 -0
- claude_mpm/agents/CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md +1 -1
- claude_mpm/agents/MEMORY.md +1 -1
- claude_mpm/agents/PM_INSTRUCTIONS.md +843 -900
- claude_mpm/agents/WORKFLOW.md +5 -254
- claude_mpm/agents/agent_loader.py +13 -44
- claude_mpm/agents/base_agent.json +1 -1
- claude_mpm/agents/frontmatter_validator.py +2 -2
- claude_mpm/agents/templates/circuit-breakers.md +138 -1
- claude_mpm/cli/__main__.py +4 -0
- claude_mpm/cli/chrome_devtools_installer.py +175 -0
- claude_mpm/cli/commands/agent_state_manager.py +18 -27
- claude_mpm/cli/commands/agents.py +9 -40
- claude_mpm/cli/commands/auto_configure.py +210 -25
- claude_mpm/cli/commands/config.py +88 -2
- claude_mpm/cli/commands/configure.py +1098 -159
- claude_mpm/cli/commands/configure_agent_display.py +25 -6
- claude_mpm/cli/commands/mpm_init/core.py +225 -46
- claude_mpm/cli/commands/mpm_init/knowledge_extractor.py +481 -0
- claude_mpm/cli/commands/mpm_init/prompts.py +280 -0
- claude_mpm/cli/commands/postmortem.py +1 -1
- claude_mpm/cli/commands/profile.py +277 -0
- claude_mpm/cli/commands/skills.py +218 -197
- claude_mpm/cli/commands/summarize.py +413 -0
- claude_mpm/cli/executor.py +21 -3
- claude_mpm/cli/interactive/agent_wizard.py +2 -2
- claude_mpm/cli/parsers/agents_parser.py +0 -9
- claude_mpm/cli/parsers/auto_configure_parser.py +0 -138
- claude_mpm/cli/parsers/base_parser.py +12 -0
- claude_mpm/cli/parsers/config_parser.py +153 -83
- claude_mpm/cli/parsers/profile_parser.py +148 -0
- claude_mpm/cli/parsers/skills_parser.py +0 -5
- claude_mpm/cli/startup.py +876 -149
- claude_mpm/commands/mpm-config.md +28 -0
- claude_mpm/commands/mpm-doctor.md +9 -22
- claude_mpm/commands/mpm-help.md +5 -287
- claude_mpm/commands/mpm-init.md +81 -507
- claude_mpm/commands/mpm-monitor.md +15 -402
- claude_mpm/commands/mpm-organize.md +120 -0
- 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/config/agent_sources.py +27 -0
- claude_mpm/core/config.py +2 -4
- claude_mpm/core/framework/formatters/content_formatter.py +3 -13
- claude_mpm/core/framework/loaders/agent_loader.py +8 -5
- claude_mpm/core/framework/loaders/instruction_loader.py +52 -11
- claude_mpm/core/framework_loader.py +4 -2
- claude_mpm/core/logger.py +13 -0
- claude_mpm/core/optimized_startup.py +59 -0
- claude_mpm/core/shared/config_loader.py +1 -1
- claude_mpm/core/socketio_pool.py +3 -3
- claude_mpm/core/unified_agent_registry.py +5 -15
- claude_mpm/dashboard/static/svelte-build/_app/env.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.B_FtCwCQ.css +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.Cl_eSA4x.css +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BgChzWQ1.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CIXEwuWe.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CWc5urbQ.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DMkZpdF2.js +2 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DjhvlsAc.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/N4qtv3Hx.js +2 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/uj46x2Wr.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/app.DTL5mJO-.js +2 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.DzuEhzqh.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/0.CAGBuiOw.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/1.DFLC8jdE.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.DPvEihJJ.js +10 -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/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/correlation_manager.py +60 -0
- claude_mpm/hooks/claude_hooks/event_handlers.py +211 -78
- claude_mpm/hooks/claude_hooks/hook_handler.py +155 -1
- 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/claude_hooks/services/__pycache__/__init__.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager.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 +30 -6
- claude_mpm/hooks/kuzu_memory_hook.py +5 -5
- claude_mpm/hooks/memory_integration_hook.py +46 -1
- claude_mpm/init.py +63 -19
- claude_mpm/models/git_repository.py +3 -3
- claude_mpm/scripts/claude-hook-handler.sh +58 -18
- claude_mpm/scripts/launch_monitor.py +93 -13
- claude_mpm/services/agents/agent_builder.py +3 -3
- claude_mpm/services/agents/agent_recommendation_service.py +278 -0
- claude_mpm/services/agents/agent_review_service.py +280 -0
- 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 -5
- claude_mpm/services/agents/deployment/agent_format_converter.py +23 -13
- claude_mpm/services/agents/deployment/agent_template_builder.py +32 -20
- 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 +247 -35
- claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +392 -87
- claude_mpm/services/agents/git_source_manager.py +53 -4
- claude_mpm/services/agents/loading/base_agent_manager.py +1 -13
- 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 +120 -7
- claude_mpm/services/agents/startup_sync.py +22 -2
- claude_mpm/services/agents/toolchain_detector.py +10 -6
- claude_mpm/services/analysis/__init__.py +11 -1
- claude_mpm/services/analysis/clone_detector.py +1030 -0
- claude_mpm/services/command_deployment_service.py +81 -10
- claude_mpm/services/diagnostics/checks/agent_check.py +2 -2
- claude_mpm/services/diagnostics/checks/agent_sources_check.py +1 -1
- claude_mpm/services/event_bus/config.py +3 -1
- claude_mpm/services/git/git_operations_service.py +101 -16
- claude_mpm/services/monitor/daemon.py +9 -2
- claude_mpm/services/monitor/daemon_manager.py +39 -3
- claude_mpm/services/monitor/management/lifecycle.py +8 -1
- claude_mpm/services/monitor/server.py +698 -22
- claude_mpm/services/pm_skills_deployer.py +711 -0
- claude_mpm/services/profile_manager.py +331 -0
- 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 +130 -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 +127 -9
- claude_mpm/services/socketio/dashboard_server.py +1 -0
- claude_mpm/services/socketio/event_normalizer.py +51 -6
- claude_mpm/services/socketio/server/core.py +386 -108
- claude_mpm/services/version_control/git_operations.py +103 -0
- claude_mpm/skills/skill_manager.py +92 -3
- claude_mpm/utils/agent_dependency_loader.py +14 -2
- claude_mpm/utils/agent_filters.py +17 -44
- claude_mpm/utils/migration.py +4 -4
- claude_mpm/utils/robust_installer.py +47 -3
- {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.48.dist-info}/METADATA +53 -87
- {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.48.dist-info}/RECORD +157 -197
- claude_mpm-5.4.48.dist-info/entry_points.txt +5 -0
- claude_mpm-5.4.48.dist-info/licenses/LICENSE +94 -0
- claude_mpm-5.4.48.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_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_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/commands/mpm-ticket-organize.md +0 -304
- claude_mpm/dashboard/analysis_runner.py +0 -455
- claude_mpm/dashboard/index.html +0 -13
- claude_mpm/dashboard/open_dashboard.py +0 -66
- claude_mpm/dashboard/static/css/activity.css +0 -1958
- claude_mpm/dashboard/static/css/connection-status.css +0 -370
- claude_mpm/dashboard/static/css/dashboard.css +0 -4701
- claude_mpm/dashboard/static/js/components/activity-tree.js +0 -1871
- claude_mpm/dashboard/static/js/components/agent-hierarchy.js +0 -777
- claude_mpm/dashboard/static/js/components/agent-inference.js +0 -956
- claude_mpm/dashboard/static/js/components/build-tracker.js +0 -333
- claude_mpm/dashboard/static/js/components/code-simple.js +0 -857
- claude_mpm/dashboard/static/js/components/connection-debug.js +0 -654
- claude_mpm/dashboard/static/js/components/diff-viewer.js +0 -891
- claude_mpm/dashboard/static/js/components/event-processor.js +0 -542
- claude_mpm/dashboard/static/js/components/event-viewer.js +0 -1155
- claude_mpm/dashboard/static/js/components/export-manager.js +0 -368
- claude_mpm/dashboard/static/js/components/file-change-tracker.js +0 -443
- claude_mpm/dashboard/static/js/components/file-change-viewer.js +0 -690
- claude_mpm/dashboard/static/js/components/file-tool-tracker.js +0 -724
- claude_mpm/dashboard/static/js/components/file-viewer.js +0 -580
- claude_mpm/dashboard/static/js/components/hud-library-loader.js +0 -211
- claude_mpm/dashboard/static/js/components/hud-manager.js +0 -671
- claude_mpm/dashboard/static/js/components/hud-visualizer.js +0 -1718
- claude_mpm/dashboard/static/js/components/module-viewer.js +0 -2764
- claude_mpm/dashboard/static/js/components/session-manager.js +0 -579
- claude_mpm/dashboard/static/js/components/socket-manager.js +0 -368
- claude_mpm/dashboard/static/js/components/ui-state-manager.js +0 -749
- claude_mpm/dashboard/static/js/components/unified-data-viewer.js +0 -1824
- claude_mpm/dashboard/static/js/components/working-directory.js +0 -920
- claude_mpm/dashboard/static/js/connection-manager.js +0 -536
- claude_mpm/dashboard/static/js/dashboard.js +0 -1914
- claude_mpm/dashboard/static/js/extension-error-handler.js +0 -164
- claude_mpm/dashboard/static/js/socket-client.js +0 -1474
- claude_mpm/dashboard/static/js/tab-isolation-fix.js +0 -185
- claude_mpm/dashboard/static/socket.io.min.js +0 -7
- claude_mpm/dashboard/static/socket.io.v4.8.1.backup.js +0 -7
- claude_mpm/dashboard/templates/code_simple.html +0 -153
- claude_mpm/dashboard/templates/index.html +0 -606
- claude_mpm/dashboard/test_dashboard.html +0 -372
- claude_mpm/scripts/mcp_server.py +0 -75
- claude_mpm/scripts/mcp_wrapper.py +0 -39
- claude_mpm/services/mcp_gateway/__init__.py +0 -159
- claude_mpm/services/mcp_gateway/auto_configure.py +0 -369
- claude_mpm/services/mcp_gateway/config/__init__.py +0 -17
- claude_mpm/services/mcp_gateway/config/config_loader.py +0 -296
- claude_mpm/services/mcp_gateway/config/config_schema.py +0 -243
- claude_mpm/services/mcp_gateway/config/configuration.py +0 -429
- claude_mpm/services/mcp_gateway/core/__init__.py +0 -43
- claude_mpm/services/mcp_gateway/core/base.py +0 -312
- claude_mpm/services/mcp_gateway/core/exceptions.py +0 -253
- claude_mpm/services/mcp_gateway/core/interfaces.py +0 -443
- claude_mpm/services/mcp_gateway/core/process_pool.py +0 -977
- claude_mpm/services/mcp_gateway/core/singleton_manager.py +0 -315
- claude_mpm/services/mcp_gateway/core/startup_verification.py +0 -316
- claude_mpm/services/mcp_gateway/main.py +0 -589
- claude_mpm/services/mcp_gateway/registry/__init__.py +0 -12
- claude_mpm/services/mcp_gateway/registry/service_registry.py +0 -412
- claude_mpm/services/mcp_gateway/registry/tool_registry.py +0 -489
- claude_mpm/services/mcp_gateway/server/__init__.py +0 -15
- claude_mpm/services/mcp_gateway/server/mcp_gateway.py +0 -414
- claude_mpm/services/mcp_gateway/server/stdio_handler.py +0 -372
- claude_mpm/services/mcp_gateway/server/stdio_server.py +0 -712
- claude_mpm/services/mcp_gateway/tools/__init__.py +0 -36
- claude_mpm/services/mcp_gateway/tools/base_adapter.py +0 -485
- claude_mpm/services/mcp_gateway/tools/document_summarizer.py +0 -789
- claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +0 -654
- claude_mpm/services/mcp_gateway/tools/health_check_tool.py +0 -456
- claude_mpm/services/mcp_gateway/tools/hello_world.py +0 -551
- claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +0 -555
- claude_mpm/services/mcp_gateway/utils/__init__.py +0 -14
- claude_mpm/services/mcp_gateway/utils/package_version_checker.py +0 -160
- claude_mpm/services/mcp_gateway/utils/update_preferences.py +0 -170
- claude_mpm-5.1.9.dist-info/entry_points.txt +0 -10
- claude_mpm-5.1.9.dist-info/licenses/LICENSE +0 -21
- {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.48.dist-info}/WHEEL +0 -0
- {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.48.dist-info}/top_level.txt +0 -0
claude_mpm/cli/startup.py
CHANGED
|
@@ -10,47 +10,128 @@ 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
|
|
|
17
|
-
def
|
|
18
|
-
"""
|
|
16
|
+
def sync_hooks_on_startup(quiet: bool = False) -> bool:
|
|
17
|
+
"""Ensure hooks are up-to-date on startup.
|
|
18
|
+
|
|
19
|
+
WHY: Users can have stale hook configurations in settings.json that cause errors.
|
|
20
|
+
Reinstalling hooks ensures the hook format matches the current code.
|
|
19
21
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
DESIGN DECISION: Shows brief status message on success for user awareness.
|
|
23
|
+
Failures are logged but don't prevent startup to ensure claude-mpm remains functional.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
quiet: If True, suppress all output (used internally)
|
|
23
27
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
- Migration guidance: Provides clear path to migrate
|
|
27
|
-
- One-time check: Only warns if legacy cache contains files
|
|
28
|
+
Returns:
|
|
29
|
+
bool: True if hooks were synced successfully, False otherwise
|
|
28
30
|
"""
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
canonical_cache = home / ".claude-mpm" / "cache" / "remote-agents"
|
|
32
|
-
migration_marker = home / ".claude-mpm" / "cache" / ".migrated_to_remote_agents"
|
|
31
|
+
try:
|
|
32
|
+
from ..hooks.claude_hooks.installer import HookInstaller
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
installer = HookInstaller()
|
|
35
|
+
|
|
36
|
+
# Show brief status (hooks sync is fast)
|
|
37
|
+
if not quiet:
|
|
38
|
+
print("Syncing Claude Code hooks...", end=" ", flush=True)
|
|
39
|
+
|
|
40
|
+
# Reinstall hooks (force=True ensures update)
|
|
41
|
+
success = installer.install_hooks(force=True)
|
|
42
|
+
|
|
43
|
+
if not quiet:
|
|
44
|
+
if success:
|
|
45
|
+
print("✓")
|
|
46
|
+
else:
|
|
47
|
+
print("(skipped)")
|
|
48
|
+
|
|
49
|
+
return success
|
|
50
|
+
|
|
51
|
+
except Exception as e:
|
|
52
|
+
if not quiet:
|
|
53
|
+
print("(error)")
|
|
54
|
+
# Log but don't fail startup
|
|
55
|
+
from ..core.logger import get_logger
|
|
37
56
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
57
|
+
logger = get_logger("startup")
|
|
58
|
+
logger.warning(f"Hook sync failed (non-fatal): {e}")
|
|
59
|
+
return False
|
|
60
|
+
|
|
61
|
+
|
|
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.
|
|
68
|
+
|
|
69
|
+
Old structure (removed):
|
|
70
|
+
~/.claude-mpm/cache/agents/engineer/
|
|
71
|
+
~/.claude-mpm/cache/agents/ops/
|
|
72
|
+
~/.claude-mpm/cache/agents/qa/
|
|
73
|
+
...
|
|
74
|
+
|
|
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.
|
|
81
|
+
"""
|
|
82
|
+
import shutil
|
|
83
|
+
from pathlib import Path
|
|
84
|
+
|
|
85
|
+
from ..core.logger import get_logger
|
|
86
|
+
|
|
87
|
+
logger = get_logger("startup")
|
|
88
|
+
|
|
89
|
+
cache_dir = Path.home() / ".claude-mpm" / "cache" / "agents"
|
|
90
|
+
if not cache_dir.exists():
|
|
41
91
|
return
|
|
42
92
|
|
|
43
|
-
#
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
+
"""
|
|
54
135
|
|
|
55
136
|
|
|
56
137
|
def setup_early_environment(argv):
|
|
@@ -221,78 +302,157 @@ def discover_and_link_runtime_skills():
|
|
|
221
302
|
|
|
222
303
|
def deploy_output_style_on_startup():
|
|
223
304
|
"""
|
|
224
|
-
Deploy claude-mpm output
|
|
305
|
+
Deploy claude-mpm output styles to PROJECT-LEVEL directory on CLI startup.
|
|
306
|
+
|
|
307
|
+
WHY: Automatically deploy output styles to ensure consistent, professional
|
|
308
|
+
communication without emojis and exclamation points. Styles are project-specific
|
|
309
|
+
to allow different projects to have different communication styles.
|
|
225
310
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
311
|
+
DESIGN DECISION: This is non-blocking and idempotent. Deploys to project-level
|
|
312
|
+
directory (.claude/settings/output-styles/) instead of user-level to maintain
|
|
313
|
+
project isolation.
|
|
229
314
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
315
|
+
Deploys two styles:
|
|
316
|
+
- claude-mpm-style.md (professional mode)
|
|
317
|
+
- claude-mpm-teacher.md (teaching mode)
|
|
233
318
|
"""
|
|
234
319
|
try:
|
|
320
|
+
import shutil
|
|
235
321
|
from pathlib import Path
|
|
236
322
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
#
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
# File is empty, need to redeploy with content
|
|
259
|
-
pass # Fall through to deployment below
|
|
260
|
-
else:
|
|
261
|
-
# File has content, check if already active
|
|
262
|
-
settings = json.loads(settings_file.read_text())
|
|
263
|
-
if settings.get("activeOutputStyle") == "claude-mpm":
|
|
264
|
-
# Already deployed and active with content
|
|
265
|
-
already_configured = True
|
|
266
|
-
except Exception:
|
|
267
|
-
pass # Continue with deployment if we can't read settings
|
|
323
|
+
# Source files (in framework package)
|
|
324
|
+
package_dir = Path(__file__).parent.parent / "agents"
|
|
325
|
+
professional_source = package_dir / "CLAUDE_MPM_OUTPUT_STYLE.md"
|
|
326
|
+
teacher_source = package_dir / "CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md"
|
|
327
|
+
|
|
328
|
+
# Target directory (PROJECT-LEVEL, not user-level)
|
|
329
|
+
project_dir = Path.cwd()
|
|
330
|
+
output_styles_dir = project_dir / ".claude" / "settings" / "output-styles"
|
|
331
|
+
professional_target = output_styles_dir / "claude-mpm-style.md"
|
|
332
|
+
teacher_target = output_styles_dir / "claude-mpm-teacher.md"
|
|
333
|
+
|
|
334
|
+
# Create directory if it doesn't exist
|
|
335
|
+
output_styles_dir.mkdir(parents=True, exist_ok=True)
|
|
336
|
+
|
|
337
|
+
# Check if already deployed (both files exist and have content)
|
|
338
|
+
already_deployed = (
|
|
339
|
+
professional_target.exists()
|
|
340
|
+
and teacher_target.exists()
|
|
341
|
+
and professional_target.stat().st_size > 0
|
|
342
|
+
and teacher_target.stat().st_size > 0
|
|
343
|
+
)
|
|
268
344
|
|
|
269
|
-
if
|
|
270
|
-
# Show feedback that output
|
|
271
|
-
print("✓ Output
|
|
345
|
+
if already_deployed:
|
|
346
|
+
# Show feedback that output styles are ready
|
|
347
|
+
print("✓ Output styles ready", flush=True)
|
|
272
348
|
return
|
|
273
349
|
|
|
274
|
-
#
|
|
275
|
-
|
|
350
|
+
# Deploy both styles
|
|
351
|
+
deployed_count = 0
|
|
352
|
+
if professional_source.exists():
|
|
353
|
+
shutil.copy2(professional_source, professional_target)
|
|
354
|
+
deployed_count += 1
|
|
276
355
|
|
|
277
|
-
if
|
|
278
|
-
|
|
279
|
-
|
|
356
|
+
if teacher_source.exists():
|
|
357
|
+
shutil.copy2(teacher_source, teacher_target)
|
|
358
|
+
deployed_count += 1
|
|
280
359
|
|
|
281
|
-
|
|
360
|
+
if deployed_count > 0:
|
|
361
|
+
print(f"✓ Output styles deployed ({deployed_count} styles)", flush=True)
|
|
362
|
+
else:
|
|
363
|
+
# Source files missing - log but don't fail
|
|
364
|
+
from ..core.logger import get_logger
|
|
282
365
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
print("✓ Output style configured", flush=True)
|
|
366
|
+
logger = get_logger("cli")
|
|
367
|
+
logger.debug("Output style source files not found")
|
|
286
368
|
|
|
287
369
|
except Exception as e:
|
|
288
370
|
# Non-critical - log but don't fail startup
|
|
289
371
|
from ..core.logger import get_logger
|
|
290
372
|
|
|
291
373
|
logger = get_logger("cli")
|
|
292
|
-
logger.debug(f"Failed to deploy output
|
|
374
|
+
logger.debug(f"Failed to deploy output styles: {e}")
|
|
293
375
|
# Continue execution - output style deployment shouldn't block startup
|
|
294
376
|
|
|
295
377
|
|
|
378
|
+
def _cleanup_orphaned_agents(deploy_target: Path, deployed_agents: list[str]) -> int:
|
|
379
|
+
"""Remove agents that are managed by claude-mpm but no longer deployed.
|
|
380
|
+
|
|
381
|
+
WHY: When agent configurations change, old agents should be removed to avoid
|
|
382
|
+
confusion and stale agent references. Only removes claude-mpm managed agents,
|
|
383
|
+
leaving user-created agents untouched.
|
|
384
|
+
|
|
385
|
+
SAFETY: Only removes files with claude-mpm ownership markers in frontmatter.
|
|
386
|
+
Files without frontmatter or without ownership indicators are preserved.
|
|
387
|
+
|
|
388
|
+
Args:
|
|
389
|
+
deploy_target: Path to .claude/agents/ directory
|
|
390
|
+
deployed_agents: List of agent filenames that should remain
|
|
391
|
+
|
|
392
|
+
Returns:
|
|
393
|
+
Number of agents removed
|
|
394
|
+
"""
|
|
395
|
+
import re
|
|
396
|
+
|
|
397
|
+
import yaml
|
|
398
|
+
|
|
399
|
+
from ..core.logger import get_logger
|
|
400
|
+
|
|
401
|
+
logger = get_logger("cli")
|
|
402
|
+
removed_count = 0
|
|
403
|
+
deployed_set = set(deployed_agents)
|
|
404
|
+
|
|
405
|
+
if not deploy_target.exists():
|
|
406
|
+
return 0
|
|
407
|
+
|
|
408
|
+
# Scan all .md files in agents directory
|
|
409
|
+
for agent_file in deploy_target.glob("*.md"):
|
|
410
|
+
# Skip hidden files
|
|
411
|
+
if agent_file.name.startswith("."):
|
|
412
|
+
continue
|
|
413
|
+
|
|
414
|
+
# Skip if this agent should remain deployed
|
|
415
|
+
if agent_file.name in deployed_set:
|
|
416
|
+
continue
|
|
417
|
+
|
|
418
|
+
# Check if this is a claude-mpm managed agent
|
|
419
|
+
try:
|
|
420
|
+
content = agent_file.read_text(encoding="utf-8")
|
|
421
|
+
|
|
422
|
+
# Parse YAML frontmatter
|
|
423
|
+
if content.startswith("---"):
|
|
424
|
+
match = re.match(r"^---\n(.*?)\n---", content, re.DOTALL)
|
|
425
|
+
if match:
|
|
426
|
+
frontmatter = yaml.safe_load(match.group(1))
|
|
427
|
+
|
|
428
|
+
# Check ownership indicators
|
|
429
|
+
is_ours = False
|
|
430
|
+
if frontmatter:
|
|
431
|
+
author = frontmatter.get("author", "")
|
|
432
|
+
source = frontmatter.get("source", "")
|
|
433
|
+
agent_id = frontmatter.get("agent_id", "")
|
|
434
|
+
|
|
435
|
+
# It's ours if it has any of these markers
|
|
436
|
+
if (
|
|
437
|
+
"Claude MPM" in str(author)
|
|
438
|
+
or source == "remote"
|
|
439
|
+
or agent_id
|
|
440
|
+
):
|
|
441
|
+
is_ours = True
|
|
442
|
+
|
|
443
|
+
if is_ours:
|
|
444
|
+
# Safe to remove - it's our agent but not deployed
|
|
445
|
+
agent_file.unlink()
|
|
446
|
+
removed_count += 1
|
|
447
|
+
logger.info(f"Removed orphaned agent: {agent_file.name}")
|
|
448
|
+
|
|
449
|
+
except Exception as e:
|
|
450
|
+
logger.debug(f"Could not check agent {agent_file.name}: {e}")
|
|
451
|
+
# Don't remove if we can't verify ownership
|
|
452
|
+
|
|
453
|
+
return removed_count
|
|
454
|
+
|
|
455
|
+
|
|
296
456
|
def sync_remote_agents_on_startup():
|
|
297
457
|
"""
|
|
298
458
|
Synchronize agent templates from remote sources on startup.
|
|
@@ -306,18 +466,47 @@ def sync_remote_agents_on_startup():
|
|
|
306
466
|
block startup to ensure claude-mpm remains functional.
|
|
307
467
|
|
|
308
468
|
Workflow:
|
|
309
|
-
1.
|
|
310
|
-
2.
|
|
311
|
-
3.
|
|
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
|
|
312
474
|
"""
|
|
313
|
-
#
|
|
475
|
+
# Cleanup legacy cache directories first (before syncing)
|
|
476
|
+
cleanup_legacy_agent_cache()
|
|
477
|
+
|
|
478
|
+
# DEPRECATED: Legacy warning - replaced by automatic cleanup above
|
|
314
479
|
check_legacy_cache()
|
|
315
480
|
|
|
316
481
|
try:
|
|
482
|
+
from ..core.shared.config_loader import ConfigLoader
|
|
317
483
|
from ..services.agents.deployment.agent_deployment import AgentDeploymentService
|
|
318
484
|
from ..services.agents.startup_sync import sync_agents_on_startup
|
|
485
|
+
from ..services.profile_manager import ProfileManager
|
|
319
486
|
from ..utils.progress import ProgressBar
|
|
320
487
|
|
|
488
|
+
# Load active profile if configured
|
|
489
|
+
# Get project root (where .claude-mpm exists)
|
|
490
|
+
from pathlib import Path
|
|
491
|
+
project_root = Path.cwd()
|
|
492
|
+
|
|
493
|
+
profile_manager = ProfileManager(project_dir=project_root)
|
|
494
|
+
config_loader = ConfigLoader()
|
|
495
|
+
main_config = config_loader.load_main_config()
|
|
496
|
+
active_profile = main_config.get("active_profile")
|
|
497
|
+
|
|
498
|
+
if active_profile:
|
|
499
|
+
success = profile_manager.load_profile(active_profile)
|
|
500
|
+
if success:
|
|
501
|
+
summary = profile_manager.get_filtering_summary()
|
|
502
|
+
from ..core.logger import get_logger
|
|
503
|
+
|
|
504
|
+
logger = get_logger("cli")
|
|
505
|
+
logger.info(
|
|
506
|
+
f"Profile '{active_profile}' active: "
|
|
507
|
+
f"{summary['enabled_agents_count']} agents enabled"
|
|
508
|
+
)
|
|
509
|
+
|
|
321
510
|
# Phase 1: Sync files from Git sources
|
|
322
511
|
result = sync_agents_on_startup()
|
|
323
512
|
|
|
@@ -344,19 +533,122 @@ def sync_remote_agents_on_startup():
|
|
|
344
533
|
# Phase 2: Deploy agents from cache to ~/.claude/agents/
|
|
345
534
|
# This mirrors the skills deployment pattern (lines 371-407)
|
|
346
535
|
try:
|
|
347
|
-
# Initialize deployment service
|
|
348
|
-
|
|
536
|
+
# Initialize deployment service with profile-filtered configuration
|
|
537
|
+
from ..core.config import Config
|
|
538
|
+
|
|
539
|
+
deploy_config = None
|
|
540
|
+
if active_profile and profile_manager.active_profile:
|
|
541
|
+
# Create config with excluded agents based on profile
|
|
542
|
+
# Get all agents that should be excluded (not in enabled list)
|
|
543
|
+
from pathlib import Path
|
|
544
|
+
|
|
545
|
+
cache_dir = Path.home() / ".claude-mpm" / "cache" / "agents"
|
|
546
|
+
if cache_dir.exists():
|
|
547
|
+
# Find all agent files
|
|
548
|
+
# Supports both flat cache and {owner}/{repo}/agents/ structure
|
|
549
|
+
all_agent_files = [
|
|
550
|
+
f
|
|
551
|
+
for f in cache_dir.rglob("*.md")
|
|
552
|
+
if "/agents/" in str(f)
|
|
553
|
+
and f.stem.lower() != "base-agent"
|
|
554
|
+
and f.name.lower() not in {"readme.md", "changelog.md", "contributing.md"}
|
|
555
|
+
]
|
|
556
|
+
|
|
557
|
+
# Build exclusion list for agents not in profile
|
|
558
|
+
excluded_agents = []
|
|
559
|
+
for agent_file in all_agent_files:
|
|
560
|
+
agent_name = agent_file.stem
|
|
561
|
+
if not profile_manager.is_agent_enabled(agent_name):
|
|
562
|
+
excluded_agents.append(agent_name)
|
|
563
|
+
|
|
564
|
+
if excluded_agents:
|
|
565
|
+
# Get singleton config and update with profile settings
|
|
566
|
+
# BUGFIX: Config is a singleton that ignores dict parameter if already initialized.
|
|
567
|
+
# Creating Config({...}) doesn't store excluded_agents - use set() instead.
|
|
568
|
+
deploy_config = Config()
|
|
569
|
+
deploy_config.set("agent_deployment.excluded_agents", excluded_agents)
|
|
570
|
+
deploy_config.set("agent_deployment.filter_non_mpm_agents", False)
|
|
571
|
+
deploy_config.set("agent_deployment.case_sensitive", False)
|
|
572
|
+
deploy_config.set("agent_deployment.exclude_dependencies", False)
|
|
573
|
+
logger.info(
|
|
574
|
+
f"Profile '{active_profile}': Excluding {len(excluded_agents)} agents from deployment"
|
|
575
|
+
)
|
|
576
|
+
|
|
577
|
+
deployment_service = AgentDeploymentService(config=deploy_config)
|
|
349
578
|
|
|
350
579
|
# Count agents in cache to show accurate progress
|
|
351
580
|
from pathlib import Path
|
|
352
581
|
|
|
353
|
-
cache_dir = Path.home() / ".claude-mpm" / "cache" / "
|
|
582
|
+
cache_dir = Path.home() / ".claude-mpm" / "cache" / "agents"
|
|
354
583
|
agent_count = 0
|
|
355
584
|
|
|
356
585
|
if cache_dir.exists():
|
|
357
|
-
#
|
|
358
|
-
#
|
|
359
|
-
#
|
|
586
|
+
# BUGFIX (cache-count-inflation): Clean up stale cache files
|
|
587
|
+
# from old repositories before counting to prevent inflated counts.
|
|
588
|
+
# Issue: Old caches like bobmatnyc/claude-mpm-agents/agents/
|
|
589
|
+
# were counted alongside current agents, inflating count
|
|
590
|
+
# from 44 to 85.
|
|
591
|
+
#
|
|
592
|
+
# Solution: Remove files with nested /agents/ paths
|
|
593
|
+
# (e.g., cache/agents/user/repo/agents/...)
|
|
594
|
+
# Keep only current agents (e.g., cache/agents/engineer/...)
|
|
595
|
+
removed_count = 0
|
|
596
|
+
stale_dirs = set()
|
|
597
|
+
|
|
598
|
+
for md_file in cache_dir.rglob("*.md"):
|
|
599
|
+
# Stale cache files have multiple /agents/ in their path
|
|
600
|
+
# Current: ~/.claude-mpm/cache/agents/engineer/...
|
|
601
|
+
# (1 occurrence)
|
|
602
|
+
# Old: ~/.claude-mpm/cache/agents/bobmatnyc/.../agents/...
|
|
603
|
+
# (2+ occurrences)
|
|
604
|
+
if str(md_file).count("/agents/") > 1:
|
|
605
|
+
# Track parent directory for cleanup
|
|
606
|
+
# Extract subdirectory under cache/agents/
|
|
607
|
+
# (e.g., "bobmatnyc")
|
|
608
|
+
parts = md_file.parts
|
|
609
|
+
cache_agents_idx = parts.index("agents")
|
|
610
|
+
if cache_agents_idx + 1 < len(parts):
|
|
611
|
+
stale_subdir = parts[cache_agents_idx + 1]
|
|
612
|
+
# Only remove if it's not a known category directory
|
|
613
|
+
if stale_subdir not in [
|
|
614
|
+
"engineer",
|
|
615
|
+
"ops",
|
|
616
|
+
"qa",
|
|
617
|
+
"universal",
|
|
618
|
+
"documentation",
|
|
619
|
+
"claude-mpm",
|
|
620
|
+
"security",
|
|
621
|
+
]:
|
|
622
|
+
stale_dirs.add(cache_dir / stale_subdir)
|
|
623
|
+
|
|
624
|
+
md_file.unlink()
|
|
625
|
+
removed_count += 1
|
|
626
|
+
|
|
627
|
+
# Remove empty stale directories
|
|
628
|
+
for stale_dir in stale_dirs:
|
|
629
|
+
if stale_dir.exists() and stale_dir.is_dir():
|
|
630
|
+
try:
|
|
631
|
+
# Remove directory and all contents
|
|
632
|
+
import shutil
|
|
633
|
+
|
|
634
|
+
shutil.rmtree(stale_dir)
|
|
635
|
+
except Exception:
|
|
636
|
+
pass # Ignore cleanup errors
|
|
637
|
+
|
|
638
|
+
if removed_count > 0:
|
|
639
|
+
from loguru import logger
|
|
640
|
+
|
|
641
|
+
logger.info(
|
|
642
|
+
f"Cleaned up {removed_count} stale cache files "
|
|
643
|
+
f"from old repositories"
|
|
644
|
+
)
|
|
645
|
+
|
|
646
|
+
# Count MD files in cache (agent markdown files from
|
|
647
|
+
# current repos)
|
|
648
|
+
# BUGFIX: Only count files in agent directories,
|
|
649
|
+
# not docs/templates/READMEs
|
|
650
|
+
# Valid agent paths must contain "/agents/" exactly ONCE
|
|
651
|
+
# (current structure)
|
|
360
652
|
# Exclude PM templates, BASE-AGENT, and documentation files
|
|
361
653
|
pm_templates = {
|
|
362
654
|
"base-agent.md",
|
|
@@ -379,61 +671,122 @@ def sync_remote_agents_on_startup():
|
|
|
379
671
|
"auto-deploy-index.md",
|
|
380
672
|
}
|
|
381
673
|
|
|
382
|
-
# Find all markdown files
|
|
674
|
+
# Find all markdown files (after cleanup)
|
|
383
675
|
all_md_files = list(cache_dir.rglob("*.md"))
|
|
384
676
|
|
|
385
677
|
# Filter to only agent files:
|
|
386
|
-
# 1. Must have "/agents/" in path (
|
|
678
|
+
# 1. Must have "/agents/" in path (current structure supports
|
|
679
|
+
# both flat and {owner}/{repo}/agents/ patterns)
|
|
387
680
|
# 2. Must not be in PM templates or doc files
|
|
388
681
|
# 3. Exclude BASE-AGENT.md which is not a deployable agent
|
|
682
|
+
# 4. Exclude build artifacts (dist/, build/, .cache/)
|
|
683
|
+
# to prevent double-counting
|
|
389
684
|
agent_files = [
|
|
390
685
|
f
|
|
391
686
|
for f in all_md_files
|
|
392
687
|
if (
|
|
393
|
-
# Must be in an agent directory
|
|
688
|
+
# Must be in an agent directory
|
|
689
|
+
# Supports: cache/agents/{category}/... (flat)
|
|
690
|
+
# Supports: cache/agents/{owner}/{repo}/agents/{category}/... (GitHub sync)
|
|
394
691
|
"/agents/" in str(f)
|
|
395
692
|
# Exclude PM templates, doc files, and BASE-AGENT
|
|
396
693
|
and f.name.lower() not in pm_templates
|
|
397
694
|
and f.name.lower() not in doc_files
|
|
398
695
|
and f.name.lower() != "base-agent.md"
|
|
696
|
+
# Exclude build artifacts (prevents double-counting
|
|
697
|
+
# source + built files)
|
|
698
|
+
and not any(
|
|
699
|
+
part in str(f).split("/")
|
|
700
|
+
for part in ["dist", "build", ".cache"]
|
|
701
|
+
)
|
|
399
702
|
)
|
|
400
703
|
]
|
|
401
704
|
agent_count = len(agent_files)
|
|
402
705
|
|
|
403
706
|
if agent_count > 0:
|
|
404
|
-
#
|
|
405
|
-
|
|
406
|
-
total=agent_count,
|
|
407
|
-
prefix="Deploying agents",
|
|
408
|
-
show_percentage=True,
|
|
409
|
-
show_counter=True,
|
|
410
|
-
)
|
|
411
|
-
|
|
412
|
-
# Deploy agents with progress callback
|
|
413
|
-
deploy_target = Path.home() / ".claude" / "agents"
|
|
707
|
+
# Deploy agents to project-level directory where Claude Code expects them
|
|
708
|
+
deploy_target = Path.cwd() / ".claude" / "agents"
|
|
414
709
|
deployment_result = deployment_service.deploy_agents(
|
|
415
710
|
target_dir=deploy_target,
|
|
416
711
|
force_rebuild=False, # Only deploy if versions differ
|
|
417
712
|
deployment_mode="update", # Version-aware updates
|
|
713
|
+
config=deploy_config, # Pass config to respect profile filtering
|
|
418
714
|
)
|
|
419
715
|
|
|
420
|
-
#
|
|
421
|
-
deploy_progress.update(agent_count)
|
|
422
|
-
|
|
423
|
-
# Finish deployment progress bar
|
|
716
|
+
# Get actual counts from deployment result (reflects configured agents)
|
|
424
717
|
deployed = len(deployment_result.get("deployed", []))
|
|
425
718
|
updated = len(deployment_result.get("updated", []))
|
|
426
719
|
skipped = len(deployment_result.get("skipped", []))
|
|
427
|
-
|
|
720
|
+
total_configured = deployed + updated + skipped
|
|
721
|
+
|
|
722
|
+
# FALLBACK: If deployment result doesn't track skipped agents (async path),
|
|
723
|
+
# count existing agents in target directory as "already deployed"
|
|
724
|
+
# This ensures accurate reporting when agents are already up-to-date
|
|
725
|
+
if total_configured == 0 and deploy_target.exists():
|
|
726
|
+
existing_agents = list(deploy_target.glob("*.md"))
|
|
727
|
+
# Filter out non-agent files (e.g., README.md, INSTRUCTIONS.md)
|
|
728
|
+
agent_count_in_target = len(
|
|
729
|
+
[
|
|
730
|
+
f
|
|
731
|
+
for f in existing_agents
|
|
732
|
+
if not f.name.startswith(("README", "INSTRUCTIONS"))
|
|
733
|
+
]
|
|
734
|
+
)
|
|
735
|
+
if agent_count_in_target > 0:
|
|
736
|
+
# All agents already deployed - count them as skipped
|
|
737
|
+
skipped = agent_count_in_target
|
|
738
|
+
total_configured = agent_count_in_target
|
|
428
739
|
|
|
429
|
-
#
|
|
740
|
+
# Create progress bar with actual configured agent count (not raw file count)
|
|
741
|
+
deploy_progress = ProgressBar(
|
|
742
|
+
total=total_configured if total_configured > 0 else 1,
|
|
743
|
+
prefix="Deploying agents",
|
|
744
|
+
show_percentage=True,
|
|
745
|
+
show_counter=True,
|
|
746
|
+
)
|
|
747
|
+
|
|
748
|
+
# Update progress bar to completion
|
|
749
|
+
deploy_progress.update(
|
|
750
|
+
total_configured if total_configured > 0 else 1
|
|
751
|
+
)
|
|
752
|
+
|
|
753
|
+
# Cleanup orphaned agents (ours but no longer deployed)
|
|
754
|
+
# Get list of deployed agent filenames (what should remain)
|
|
755
|
+
deployed_filenames = []
|
|
756
|
+
for agent_name in deployment_result.get("deployed", []):
|
|
757
|
+
deployed_filenames.append(f"{agent_name}.md")
|
|
758
|
+
for agent_name in deployment_result.get("updated", []):
|
|
759
|
+
deployed_filenames.append(f"{agent_name}.md")
|
|
760
|
+
for agent_name in deployment_result.get("skipped", []):
|
|
761
|
+
deployed_filenames.append(f"{agent_name}.md")
|
|
762
|
+
|
|
763
|
+
# Run cleanup and get count of removed agents
|
|
764
|
+
removed = _cleanup_orphaned_agents(
|
|
765
|
+
deploy_target, deployed_filenames
|
|
766
|
+
)
|
|
767
|
+
|
|
768
|
+
# Show total configured agents (deployed + updated + already existing)
|
|
769
|
+
# Include cache count for context and removed count if any
|
|
430
770
|
if deployed > 0 or updated > 0:
|
|
771
|
+
if removed > 0:
|
|
772
|
+
deploy_progress.finish(
|
|
773
|
+
f"Complete: {deployed} new, {updated} updated, {skipped} unchanged, "
|
|
774
|
+
f"{removed} removed ({total_configured} configured from {agent_count} files in cache)"
|
|
775
|
+
)
|
|
776
|
+
else:
|
|
777
|
+
deploy_progress.finish(
|
|
778
|
+
f"Complete: {deployed} new, {updated} updated, {skipped} unchanged "
|
|
779
|
+
f"({total_configured} configured from {agent_count} files in cache)"
|
|
780
|
+
)
|
|
781
|
+
elif removed > 0:
|
|
431
782
|
deploy_progress.finish(
|
|
432
|
-
f"Complete: {
|
|
783
|
+
f"Complete: {total_configured} agents deployed, "
|
|
784
|
+
f"{removed} removed ({agent_count} files in cache)"
|
|
433
785
|
)
|
|
434
786
|
else:
|
|
435
787
|
deploy_progress.finish(
|
|
436
|
-
f"Complete: {
|
|
788
|
+
f"Complete: {total_configured} agents deployed "
|
|
789
|
+
f"({agent_count} files in cache)"
|
|
437
790
|
)
|
|
438
791
|
|
|
439
792
|
# Display deployment errors to user (not just logs)
|
|
@@ -496,16 +849,48 @@ def sync_remote_skills_on_startup():
|
|
|
496
849
|
|
|
497
850
|
Workflow:
|
|
498
851
|
1. Sync all enabled Git sources (download/cache files) - Phase 1 progress bar
|
|
499
|
-
2.
|
|
500
|
-
3.
|
|
852
|
+
2. Scan deployed agents for skill requirements → save to configuration.yaml
|
|
853
|
+
3. Resolve which skills to deploy (user_defined vs agent_referenced)
|
|
854
|
+
4. Apply profile filtering if active
|
|
855
|
+
5. Deploy resolved skills to ~/.claude/skills/ - Phase 2 progress bar
|
|
856
|
+
6. Log deployment results with source indication
|
|
501
857
|
"""
|
|
502
858
|
try:
|
|
503
859
|
from pathlib import Path
|
|
504
860
|
|
|
505
861
|
from ..config.skill_sources import SkillSourceConfiguration
|
|
862
|
+
from ..core.shared.config_loader import ConfigLoader
|
|
863
|
+
from ..services.profile_manager import ProfileManager
|
|
506
864
|
from ..services.skills.git_skill_source_manager import GitSkillSourceManager
|
|
865
|
+
from ..services.skills.selective_skill_deployer import (
|
|
866
|
+
get_required_skills_from_agents,
|
|
867
|
+
get_skills_to_deploy,
|
|
868
|
+
save_agent_skills_to_config,
|
|
869
|
+
)
|
|
507
870
|
from ..utils.progress import ProgressBar
|
|
508
871
|
|
|
872
|
+
# Load active profile if configured
|
|
873
|
+
# Get project root (where .claude-mpm exists)
|
|
874
|
+
project_root = Path.cwd()
|
|
875
|
+
|
|
876
|
+
profile_manager = ProfileManager(project_dir=project_root)
|
|
877
|
+
config_loader = ConfigLoader()
|
|
878
|
+
main_config = config_loader.load_main_config()
|
|
879
|
+
active_profile = main_config.get("active_profile")
|
|
880
|
+
|
|
881
|
+
if active_profile:
|
|
882
|
+
success = profile_manager.load_profile(active_profile)
|
|
883
|
+
if success:
|
|
884
|
+
from ..core.logger import get_logger
|
|
885
|
+
|
|
886
|
+
logger = get_logger("cli")
|
|
887
|
+
summary = profile_manager.get_filtering_summary()
|
|
888
|
+
logger.info(
|
|
889
|
+
f"Profile '{active_profile}' active: "
|
|
890
|
+
f"{summary['enabled_skills_count']} skills enabled, "
|
|
891
|
+
f"{summary['disabled_patterns_count']} patterns disabled"
|
|
892
|
+
)
|
|
893
|
+
|
|
509
894
|
config = SkillSourceConfiguration()
|
|
510
895
|
manager = GitSkillSourceManager(config)
|
|
511
896
|
|
|
@@ -523,6 +908,8 @@ def sync_remote_skills_on_startup():
|
|
|
523
908
|
|
|
524
909
|
# Discover total file count across all sources
|
|
525
910
|
total_file_count = 0
|
|
911
|
+
total_skill_dirs = 0 # Count actual skill directories (folders with SKILL.md)
|
|
912
|
+
|
|
526
913
|
for source in enabled_sources:
|
|
527
914
|
try:
|
|
528
915
|
# Parse GitHub URL
|
|
@@ -546,15 +933,26 @@ def sync_remote_skills_on_startup():
|
|
|
546
933
|
]
|
|
547
934
|
total_file_count += len(relevant_files)
|
|
548
935
|
|
|
936
|
+
# Count skill directories (unique directories containing SKILL.md)
|
|
937
|
+
skill_dirs = set()
|
|
938
|
+
for f in all_files:
|
|
939
|
+
if f.endswith("/SKILL.md"):
|
|
940
|
+
# Extract directory path
|
|
941
|
+
skill_dir = "/".join(f.split("/")[:-1])
|
|
942
|
+
skill_dirs.add(skill_dir)
|
|
943
|
+
total_skill_dirs += len(skill_dirs)
|
|
944
|
+
|
|
549
945
|
except Exception as e:
|
|
550
946
|
logger.debug(f"Failed to discover files for {source.id}: {e}")
|
|
551
947
|
# Use estimate if discovery fails
|
|
552
948
|
total_file_count += 150
|
|
949
|
+
total_skill_dirs += 50 # Estimate ~50 skills
|
|
553
950
|
|
|
554
951
|
# Create progress bar for sync phase with actual file count
|
|
952
|
+
# Note: We sync files (md, json, etc.), but will deploy skill directories
|
|
555
953
|
sync_progress = ProgressBar(
|
|
556
954
|
total=total_file_count if total_file_count > 0 else 1,
|
|
557
|
-
prefix="Syncing
|
|
955
|
+
prefix="Syncing skill files",
|
|
558
956
|
show_percentage=True,
|
|
559
957
|
show_counter=True,
|
|
560
958
|
)
|
|
@@ -571,51 +969,142 @@ def sync_remote_skills_on_startup():
|
|
|
571
969
|
|
|
572
970
|
if cached > 0:
|
|
573
971
|
sync_progress.finish(
|
|
574
|
-
f"Complete: {downloaded} downloaded, {cached} cached ({total_files}
|
|
972
|
+
f"Complete: {downloaded} downloaded, {cached} cached ({total_files} files, {total_skill_dirs} skills)"
|
|
575
973
|
)
|
|
576
974
|
else:
|
|
577
975
|
# All new downloads (first sync)
|
|
578
|
-
sync_progress.finish(
|
|
976
|
+
sync_progress.finish(
|
|
977
|
+
f"Complete: {downloaded} files downloaded ({total_skill_dirs} skills)"
|
|
978
|
+
)
|
|
579
979
|
|
|
580
|
-
# Phase 2:
|
|
581
|
-
# This
|
|
582
|
-
# into flat deployment (e.g., collaboration-dispatching-parallel-agents/SKILL.md)
|
|
980
|
+
# Phase 2: Scan agents and save to configuration.yaml
|
|
981
|
+
# This step populates configuration.yaml with agent-referenced skills
|
|
583
982
|
if results["synced_count"] > 0:
|
|
584
|
-
|
|
983
|
+
agents_dir = Path.cwd() / ".claude" / "agents"
|
|
984
|
+
|
|
985
|
+
# Scan agents for skill requirements
|
|
986
|
+
agent_skills = get_required_skills_from_agents(agents_dir)
|
|
987
|
+
|
|
988
|
+
# Save to project-level configuration.yaml
|
|
989
|
+
project_config_path = Path.cwd() / ".claude-mpm" / "configuration.yaml"
|
|
990
|
+
save_agent_skills_to_config(list(agent_skills), project_config_path)
|
|
991
|
+
|
|
992
|
+
# Phase 3: Resolve which skills to deploy (user_defined or agent_referenced)
|
|
993
|
+
skills_to_deploy, skill_source = get_skills_to_deploy(project_config_path)
|
|
994
|
+
|
|
995
|
+
# Phase 4: Apply profile filtering if active
|
|
996
|
+
if active_profile and profile_manager.active_profile:
|
|
997
|
+
# Filter skills based on profile
|
|
998
|
+
if skills_to_deploy:
|
|
999
|
+
# Filter the resolved skill list
|
|
1000
|
+
original_count = len(skills_to_deploy)
|
|
1001
|
+
filtered_skills = [
|
|
1002
|
+
skill
|
|
1003
|
+
for skill in skills_to_deploy
|
|
1004
|
+
if profile_manager.is_skill_enabled(skill)
|
|
1005
|
+
]
|
|
1006
|
+
filtered_count = original_count - len(filtered_skills)
|
|
1007
|
+
|
|
1008
|
+
# SAFEGUARD: Warn if all skills were filtered out (misconfiguration)
|
|
1009
|
+
if not filtered_skills and original_count > 0:
|
|
1010
|
+
logger.warning(
|
|
1011
|
+
f"Profile '{active_profile}' filtered ALL {original_count} skills. "
|
|
1012
|
+
f"This may indicate a naming mismatch in the profile."
|
|
1013
|
+
)
|
|
1014
|
+
elif filtered_count > 0:
|
|
1015
|
+
logger.info(
|
|
1016
|
+
f"Profile '{active_profile}' filtered {filtered_count} skills "
|
|
1017
|
+
f"({len(filtered_skills)} remaining)"
|
|
1018
|
+
)
|
|
1019
|
+
|
|
1020
|
+
skills_to_deploy = filtered_skills
|
|
1021
|
+
skill_source = f"{skill_source} + profile filtered"
|
|
1022
|
+
else:
|
|
1023
|
+
# No explicit skill list - filter from all available
|
|
1024
|
+
all_skills = manager.get_all_skills()
|
|
1025
|
+
filtered_skills = [
|
|
1026
|
+
skill["name"]
|
|
1027
|
+
for skill in all_skills
|
|
1028
|
+
if profile_manager.is_skill_enabled(skill["name"])
|
|
1029
|
+
]
|
|
1030
|
+
skills_to_deploy = filtered_skills
|
|
1031
|
+
skill_source = "profile filtered"
|
|
1032
|
+
logger.info(
|
|
1033
|
+
f"Profile '{active_profile}': "
|
|
1034
|
+
f"{len(filtered_skills)} skills enabled from {len(all_skills)} available"
|
|
1035
|
+
)
|
|
1036
|
+
|
|
1037
|
+
# Get all skills to determine counts
|
|
585
1038
|
all_skills = manager.get_all_skills()
|
|
586
|
-
|
|
1039
|
+
total_skill_count = len(all_skills)
|
|
587
1040
|
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
prefix="Deploying skill directories",
|
|
593
|
-
show_percentage=True,
|
|
594
|
-
show_counter=True,
|
|
595
|
-
)
|
|
1041
|
+
# Determine skill count based on resolution
|
|
1042
|
+
skill_count = (
|
|
1043
|
+
len(skills_to_deploy) if skills_to_deploy else total_skill_count
|
|
1044
|
+
)
|
|
596
1045
|
|
|
597
|
-
|
|
1046
|
+
if skill_count > 0:
|
|
1047
|
+
# Deploy skills with resolved filter
|
|
598
1048
|
# Deploy to project directory (like agents), not user directory
|
|
599
1049
|
deployment_result = manager.deploy_skills(
|
|
600
1050
|
target_dir=Path.cwd() / ".claude" / "skills",
|
|
601
1051
|
force=False,
|
|
602
|
-
|
|
1052
|
+
skill_filter=set(skills_to_deploy) if skills_to_deploy else None,
|
|
603
1053
|
)
|
|
604
1054
|
|
|
605
|
-
#
|
|
1055
|
+
# Get actual counts from deployment result
|
|
606
1056
|
deployed = deployment_result.get("deployed_count", 0)
|
|
607
1057
|
skipped = deployment_result.get("skipped_count", 0)
|
|
1058
|
+
filtered = deployment_result.get("filtered_count", 0)
|
|
608
1059
|
total_available = deployed + skipped
|
|
609
1060
|
|
|
1061
|
+
# Only show progress bar if there are skills to deploy
|
|
1062
|
+
if total_available > 0:
|
|
1063
|
+
deploy_progress = ProgressBar(
|
|
1064
|
+
total=total_available,
|
|
1065
|
+
prefix="Deploying skill directories",
|
|
1066
|
+
show_percentage=True,
|
|
1067
|
+
show_counter=True,
|
|
1068
|
+
)
|
|
1069
|
+
# Update progress bar to completion
|
|
1070
|
+
deploy_progress.update(total_available)
|
|
1071
|
+
else:
|
|
1072
|
+
# No skills to deploy - create dummy progress for message only
|
|
1073
|
+
deploy_progress = ProgressBar(
|
|
1074
|
+
total=1,
|
|
1075
|
+
prefix="Deploying skill directories",
|
|
1076
|
+
show_percentage=False,
|
|
1077
|
+
show_counter=False,
|
|
1078
|
+
)
|
|
1079
|
+
deploy_progress.update(1)
|
|
1080
|
+
|
|
610
1081
|
# Show total available skills (deployed + already existing)
|
|
611
|
-
#
|
|
1082
|
+
# Include source indication (user_defined vs agent_referenced)
|
|
1083
|
+
# Note: total_skill_count is from cache, total_available is what's deployed/needed
|
|
1084
|
+
source_label = (
|
|
1085
|
+
"user override" if skill_source == "user_defined" else "from agents"
|
|
1086
|
+
)
|
|
1087
|
+
|
|
612
1088
|
if deployed > 0:
|
|
1089
|
+
if filtered > 0:
|
|
1090
|
+
deploy_progress.finish(
|
|
1091
|
+
f"Complete: {deployed} new, {skipped} unchanged "
|
|
1092
|
+
f"({total_available} {source_label}, {filtered} files in cache)"
|
|
1093
|
+
)
|
|
1094
|
+
else:
|
|
1095
|
+
deploy_progress.finish(
|
|
1096
|
+
f"Complete: {deployed} new, {skipped} unchanged "
|
|
1097
|
+
f"({total_available} skills {source_label} from {total_skill_count} files in cache)"
|
|
1098
|
+
)
|
|
1099
|
+
elif filtered > 0:
|
|
1100
|
+
# Skills filtered means agents require fewer skills than available
|
|
613
1101
|
deploy_progress.finish(
|
|
614
|
-
f"
|
|
1102
|
+
f"No skills needed ({source_label}, {total_skill_count} files in cache)"
|
|
615
1103
|
)
|
|
616
1104
|
else:
|
|
617
1105
|
deploy_progress.finish(
|
|
618
|
-
f"Complete: {total_available} skills
|
|
1106
|
+
f"Complete: {total_available} skills {source_label} "
|
|
1107
|
+
f"({total_skill_count} files in cache)"
|
|
619
1108
|
)
|
|
620
1109
|
|
|
621
1110
|
# Log deployment errors if any
|
|
@@ -644,6 +1133,241 @@ def sync_remote_skills_on_startup():
|
|
|
644
1133
|
# Continue execution - skill sync failure shouldn't block startup
|
|
645
1134
|
|
|
646
1135
|
|
|
1136
|
+
def show_agent_summary():
|
|
1137
|
+
"""
|
|
1138
|
+
Display agent availability summary on startup.
|
|
1139
|
+
|
|
1140
|
+
WHY: Users should see at a glance how many agents are available and installed
|
|
1141
|
+
without having to run /mpm-agents list.
|
|
1142
|
+
|
|
1143
|
+
DESIGN DECISION: Fast, non-blocking check that counts agents from the deployment
|
|
1144
|
+
directory. Shows simple "X installed / Y available" format. Failures are silent
|
|
1145
|
+
to avoid blocking startup.
|
|
1146
|
+
"""
|
|
1147
|
+
try:
|
|
1148
|
+
from pathlib import Path
|
|
1149
|
+
|
|
1150
|
+
# Count deployed agents (installed)
|
|
1151
|
+
deploy_target = Path.cwd() / ".claude" / "agents"
|
|
1152
|
+
installed_count = 0
|
|
1153
|
+
if deploy_target.exists():
|
|
1154
|
+
# Count .md files, excluding README and other docs
|
|
1155
|
+
agent_files = [
|
|
1156
|
+
f
|
|
1157
|
+
for f in deploy_target.glob("*.md")
|
|
1158
|
+
if not f.name.startswith(("README", "INSTRUCTIONS", "."))
|
|
1159
|
+
]
|
|
1160
|
+
installed_count = len(agent_files)
|
|
1161
|
+
|
|
1162
|
+
# Count available agents in cache (from remote sources)
|
|
1163
|
+
cache_dir = Path.home() / ".claude-mpm" / "cache" / "agents"
|
|
1164
|
+
available_count = 0
|
|
1165
|
+
if cache_dir.exists():
|
|
1166
|
+
# Use same filtering logic as agent deployment (lines 486-533 in startup.py)
|
|
1167
|
+
pm_templates = {
|
|
1168
|
+
"base-agent.md",
|
|
1169
|
+
"circuit_breakers.md",
|
|
1170
|
+
"pm_examples.md",
|
|
1171
|
+
"pm_red_flags.md",
|
|
1172
|
+
"research_gate_examples.md",
|
|
1173
|
+
"response_format.md",
|
|
1174
|
+
"ticket_completeness_examples.md",
|
|
1175
|
+
"validation_templates.md",
|
|
1176
|
+
"git_file_tracking.md",
|
|
1177
|
+
}
|
|
1178
|
+
doc_files = {
|
|
1179
|
+
"readme.md",
|
|
1180
|
+
"changelog.md",
|
|
1181
|
+
"contributing.md",
|
|
1182
|
+
"implementation-summary.md",
|
|
1183
|
+
"reorganization-plan.md",
|
|
1184
|
+
"auto-deploy-index.md",
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
# Find all markdown files in agents/ directories
|
|
1188
|
+
all_md_files = list(cache_dir.rglob("*.md"))
|
|
1189
|
+
agent_files = [
|
|
1190
|
+
f
|
|
1191
|
+
for f in all_md_files
|
|
1192
|
+
if (
|
|
1193
|
+
"/agents/" in str(f)
|
|
1194
|
+
and f.name.lower() not in pm_templates
|
|
1195
|
+
and f.name.lower() not in doc_files
|
|
1196
|
+
and f.name.lower() != "base-agent.md"
|
|
1197
|
+
and not any(
|
|
1198
|
+
part in str(f).split("/")
|
|
1199
|
+
for part in ["dist", "build", ".cache"]
|
|
1200
|
+
)
|
|
1201
|
+
)
|
|
1202
|
+
]
|
|
1203
|
+
available_count = len(agent_files)
|
|
1204
|
+
|
|
1205
|
+
# Display summary if we have agents
|
|
1206
|
+
if installed_count > 0 or available_count > 0:
|
|
1207
|
+
print(
|
|
1208
|
+
f"✓ Agents: {installed_count} deployed / {max(0, available_count - installed_count)} cached",
|
|
1209
|
+
flush=True,
|
|
1210
|
+
)
|
|
1211
|
+
|
|
1212
|
+
except Exception as e:
|
|
1213
|
+
# Silent failure - agent summary is informational only
|
|
1214
|
+
from ..core.logger import get_logger
|
|
1215
|
+
|
|
1216
|
+
logger = get_logger("cli")
|
|
1217
|
+
logger.debug(f"Failed to generate agent summary: {e}")
|
|
1218
|
+
|
|
1219
|
+
|
|
1220
|
+
def show_skill_summary():
|
|
1221
|
+
"""
|
|
1222
|
+
Display skill availability summary on startup.
|
|
1223
|
+
|
|
1224
|
+
WHY: Users should see at a glance how many skills are deployed and available
|
|
1225
|
+
from collections, similar to the agent summary.
|
|
1226
|
+
|
|
1227
|
+
DESIGN DECISION: Fast, non-blocking check that counts skills from deployment
|
|
1228
|
+
directory and collection repos. Shows "X installed (Y available)" format.
|
|
1229
|
+
Failures are silent to avoid blocking startup.
|
|
1230
|
+
"""
|
|
1231
|
+
try:
|
|
1232
|
+
from pathlib import Path
|
|
1233
|
+
|
|
1234
|
+
# Count deployed skills (installed)
|
|
1235
|
+
skills_dir = Path.home() / ".claude" / "skills"
|
|
1236
|
+
installed_count = 0
|
|
1237
|
+
if skills_dir.exists():
|
|
1238
|
+
# Count directories with SKILL.md (excludes collection repos)
|
|
1239
|
+
# Exclude collection directories (obra-superpowers, etc.)
|
|
1240
|
+
skill_dirs = [
|
|
1241
|
+
d
|
|
1242
|
+
for d in skills_dir.iterdir()
|
|
1243
|
+
if d.is_dir()
|
|
1244
|
+
and (d / "SKILL.md").exists()
|
|
1245
|
+
and not (d / ".git").exists() # Exclude collection repos
|
|
1246
|
+
]
|
|
1247
|
+
installed_count = len(skill_dirs)
|
|
1248
|
+
|
|
1249
|
+
# Count available skills in collections
|
|
1250
|
+
available_count = 0
|
|
1251
|
+
if skills_dir.exists():
|
|
1252
|
+
# Scan all collection directories (those with .git)
|
|
1253
|
+
for collection_dir in skills_dir.iterdir():
|
|
1254
|
+
if (
|
|
1255
|
+
not collection_dir.is_dir()
|
|
1256
|
+
or not (collection_dir / ".git").exists()
|
|
1257
|
+
):
|
|
1258
|
+
continue
|
|
1259
|
+
|
|
1260
|
+
# Count skill directories in this collection
|
|
1261
|
+
# Skills can be nested in: skills/category/skill-name/SKILL.md
|
|
1262
|
+
# or in flat structure: skill-name/SKILL.md
|
|
1263
|
+
for root, dirs, files in os.walk(collection_dir):
|
|
1264
|
+
if "SKILL.md" in files:
|
|
1265
|
+
# Exclude build artifacts and hidden directories (within the collection)
|
|
1266
|
+
# Get relative path from collection_dir to avoid excluding based on .claude parent
|
|
1267
|
+
root_path = Path(root)
|
|
1268
|
+
relative_parts = root_path.relative_to(collection_dir).parts
|
|
1269
|
+
if not any(
|
|
1270
|
+
part.startswith(".")
|
|
1271
|
+
or part in ["dist", "build", "__pycache__"]
|
|
1272
|
+
for part in relative_parts
|
|
1273
|
+
):
|
|
1274
|
+
available_count += 1
|
|
1275
|
+
|
|
1276
|
+
# Display summary if we have skills
|
|
1277
|
+
if installed_count > 0 or available_count > 0:
|
|
1278
|
+
print(
|
|
1279
|
+
f"✓ Skills: {installed_count} installed ({available_count} available)",
|
|
1280
|
+
flush=True,
|
|
1281
|
+
)
|
|
1282
|
+
|
|
1283
|
+
except Exception as e:
|
|
1284
|
+
# Silent failure - skill summary is informational only
|
|
1285
|
+
from ..core.logger import get_logger
|
|
1286
|
+
|
|
1287
|
+
logger = get_logger("cli")
|
|
1288
|
+
logger.debug(f"Failed to generate skill summary: {e}")
|
|
1289
|
+
|
|
1290
|
+
|
|
1291
|
+
def verify_and_show_pm_skills():
|
|
1292
|
+
"""Verify PM skills and display status.
|
|
1293
|
+
|
|
1294
|
+
WHY: PM skills are essential for PM agent operation.
|
|
1295
|
+
Shows deployment status and auto-deploys if missing.
|
|
1296
|
+
"""
|
|
1297
|
+
try:
|
|
1298
|
+
from pathlib import Path
|
|
1299
|
+
|
|
1300
|
+
from ..services.pm_skills_deployer import PMSkillsDeployerService
|
|
1301
|
+
|
|
1302
|
+
deployer = PMSkillsDeployerService()
|
|
1303
|
+
project_dir = Path.cwd()
|
|
1304
|
+
|
|
1305
|
+
result = deployer.verify_pm_skills(project_dir)
|
|
1306
|
+
|
|
1307
|
+
if result.verified:
|
|
1308
|
+
# Show verified status
|
|
1309
|
+
print(f"✓ PM skills: {result.skill_count} verified", flush=True)
|
|
1310
|
+
else:
|
|
1311
|
+
# Auto-deploy if missing
|
|
1312
|
+
print("Deploying PM skills...", end="", flush=True)
|
|
1313
|
+
deploy_result = deployer.deploy_pm_skills(project_dir)
|
|
1314
|
+
if deploy_result.success:
|
|
1315
|
+
total = len(deploy_result.deployed) + len(deploy_result.skipped)
|
|
1316
|
+
print(f"\r✓ PM skills: {total} deployed" + " " * 20, flush=True)
|
|
1317
|
+
else:
|
|
1318
|
+
print(f"\r⚠ PM skills: deployment failed" + " " * 20, flush=True)
|
|
1319
|
+
|
|
1320
|
+
except ImportError:
|
|
1321
|
+
# PM skills deployer not available - skip silently
|
|
1322
|
+
pass
|
|
1323
|
+
except Exception as e:
|
|
1324
|
+
from ..core.logger import get_logger
|
|
1325
|
+
|
|
1326
|
+
logger = get_logger("cli")
|
|
1327
|
+
logger.debug(f"PM skills verification failed: {e}")
|
|
1328
|
+
|
|
1329
|
+
|
|
1330
|
+
def auto_install_chrome_devtools_on_startup():
|
|
1331
|
+
"""
|
|
1332
|
+
Automatically install chrome-devtools-mcp on startup if enabled.
|
|
1333
|
+
|
|
1334
|
+
WHY: Browser automation capabilities should be available out-of-the-box without
|
|
1335
|
+
manual MCP server configuration. chrome-devtools-mcp provides powerful browser
|
|
1336
|
+
interaction tools for Claude Code.
|
|
1337
|
+
|
|
1338
|
+
DESIGN DECISION: Non-blocking installation that doesn't prevent startup if it fails.
|
|
1339
|
+
Respects user configuration setting (enabled by default). Only installs if not
|
|
1340
|
+
already configured in Claude.
|
|
1341
|
+
"""
|
|
1342
|
+
try:
|
|
1343
|
+
# Check if auto-install is disabled in config
|
|
1344
|
+
from ..config.config_loader import ConfigLoader
|
|
1345
|
+
|
|
1346
|
+
config_loader = ConfigLoader()
|
|
1347
|
+
try:
|
|
1348
|
+
config = config_loader.load_main_config()
|
|
1349
|
+
chrome_devtools_config = config.get("chrome_devtools", {})
|
|
1350
|
+
if not chrome_devtools_config.get("auto_install", True):
|
|
1351
|
+
# Auto-install disabled, skip silently
|
|
1352
|
+
return
|
|
1353
|
+
except Exception:
|
|
1354
|
+
# If config loading fails, assume auto-install is enabled (default)
|
|
1355
|
+
pass
|
|
1356
|
+
|
|
1357
|
+
# Import and run chrome-devtools installation
|
|
1358
|
+
from ..cli.chrome_devtools_installer import auto_install_chrome_devtools
|
|
1359
|
+
|
|
1360
|
+
auto_install_chrome_devtools(quiet=False)
|
|
1361
|
+
|
|
1362
|
+
except Exception as e:
|
|
1363
|
+
# Import logger here to avoid circular imports
|
|
1364
|
+
from ..core.logger import get_logger
|
|
1365
|
+
|
|
1366
|
+
logger = get_logger("cli")
|
|
1367
|
+
logger.debug(f"Failed to auto-install chrome-devtools-mcp: {e}")
|
|
1368
|
+
# Continue execution - chrome-devtools installation failure shouldn't block startup
|
|
1369
|
+
|
|
1370
|
+
|
|
647
1371
|
def run_background_services():
|
|
648
1372
|
"""
|
|
649
1373
|
Initialize all background services on startup.
|
|
@@ -656,11 +1380,17 @@ def run_background_services():
|
|
|
656
1380
|
file creation in project .claude/ directories.
|
|
657
1381
|
See: SystemInstructionsDeployer and agent_deployment.py line 504-509
|
|
658
1382
|
"""
|
|
1383
|
+
# Sync hooks early to ensure up-to-date configuration
|
|
1384
|
+
# RATIONALE: Hooks should be synced before other services to fix stale configs
|
|
1385
|
+
# This is fast (<100ms) and non-blocking, so it doesn't delay startup
|
|
1386
|
+
sync_hooks_on_startup() # Shows "Syncing Claude Code hooks... ✓"
|
|
1387
|
+
|
|
659
1388
|
initialize_project_registry()
|
|
660
1389
|
check_mcp_auto_configuration()
|
|
661
1390
|
verify_mcp_gateway_startup()
|
|
662
1391
|
check_for_updates_async()
|
|
663
1392
|
sync_remote_agents_on_startup() # Sync agents from remote sources
|
|
1393
|
+
show_agent_summary() # Display agent counts after deployment
|
|
664
1394
|
|
|
665
1395
|
# Skills deployment order (precedence: remote > bundled)
|
|
666
1396
|
# 1. Deploy bundled skills first (base layer from package)
|
|
@@ -670,9 +1400,14 @@ def run_background_services():
|
|
|
670
1400
|
deploy_bundled_skills() # Base layer: package-bundled skills
|
|
671
1401
|
sync_remote_skills_on_startup() # Override layer: Git-based skills (takes precedence)
|
|
672
1402
|
discover_and_link_runtime_skills() # Discovery: user-added skills
|
|
1403
|
+
show_skill_summary() # Display skill counts after deployment
|
|
1404
|
+
verify_and_show_pm_skills() # PM skills verification and status
|
|
673
1405
|
|
|
674
1406
|
deploy_output_style_on_startup()
|
|
675
1407
|
|
|
1408
|
+
# Auto-install chrome-devtools-mcp for browser automation
|
|
1409
|
+
auto_install_chrome_devtools_on_startup()
|
|
1410
|
+
|
|
676
1411
|
|
|
677
1412
|
def setup_mcp_server_logging(args):
|
|
678
1413
|
"""
|
|
@@ -800,18 +1535,10 @@ def verify_mcp_gateway_startup():
|
|
|
800
1535
|
DESIGN DECISION: This is non-blocking - failures are logged but don't prevent
|
|
801
1536
|
startup to ensure claude-mpm remains functional even if MCP gateway has issues.
|
|
802
1537
|
"""
|
|
803
|
-
#
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
logger = get_logger("mcp_verify")
|
|
809
|
-
all_ok, message = verify_mcp_services_on_startup()
|
|
810
|
-
if not all_ok:
|
|
811
|
-
logger.warning(message)
|
|
812
|
-
except Exception:
|
|
813
|
-
# Non-critical - continue with startup
|
|
814
|
-
pass
|
|
1538
|
+
# DISABLED: MCP service verification removed - Claude Code handles MCP natively
|
|
1539
|
+
# The previous check warned about missing MCP services, but users should configure
|
|
1540
|
+
# MCP servers through Claude Code's native MCP management, not through claude-mpm.
|
|
1541
|
+
# See: https://docs.anthropic.com/en/docs/claude-code/mcp
|
|
815
1542
|
|
|
816
1543
|
try:
|
|
817
1544
|
import asyncio
|