claude-mpm 5.0.9__py3-none-any.whl → 5.4.41__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/{PM_INSTRUCTIONS_TEACH.md → CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md} +721 -41
- claude_mpm/agents/MEMORY.md +1 -1
- claude_mpm/agents/PM_INSTRUCTIONS.md +468 -468
- 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 +70 -2
- claude_mpm/agents/templates/circuit-breakers.md +431 -45
- claude_mpm/cli/__init__.py +0 -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 +175 -37
- claude_mpm/cli/commands/auto_configure.py +723 -236
- claude_mpm/cli/commands/config.py +88 -2
- claude_mpm/cli/commands/configure.py +1262 -157
- 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 +214 -189
- claude_mpm/cli/commands/summarize.py +413 -0
- claude_mpm/cli/executor.py +21 -3
- claude_mpm/cli/interactive/agent_wizard.py +85 -10
- claude_mpm/cli/parsers/agents_parser.py +54 -9
- claude_mpm/cli/parsers/auto_configure_parser.py +13 -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 +3 -2
- claude_mpm/cli/startup.py +879 -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/output_style_manager.py +173 -43
- claude_mpm/core/shared/config_loader.py +1 -1
- claude_mpm/core/socketio_pool.py +3 -3
- claude_mpm/core/unified_agent_registry.py +134 -16
- claude_mpm/core/unified_config.py +22 -0
- 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 +28 -0
- 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/memory_integration_hook.py +46 -1
- claude_mpm/init.py +63 -19
- claude_mpm/models/agent_definition.py +7 -0
- 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/scripts/start_activity_logging.py +0 -0
- 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_template_builder.py +5 -3
- claude_mpm/services/agents/deployment/agents_directory_resolver.py +2 -2
- claude_mpm/services/agents/deployment/multi_source_deployment_service.py +320 -29
- claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +546 -68
- claude_mpm/services/agents/git_source_manager.py +36 -2
- 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 +13 -6
- 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 +676 -0
- claude_mpm/services/profile_manager.py +331 -0
- claude_mpm/services/project/project_organizer.py +4 -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 +126 -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/gitignore.py +3 -0
- claude_mpm/utils/migration.py +4 -4
- claude_mpm/utils/robust_installer.py +47 -3
- {claude_mpm-5.0.9.dist-info → claude_mpm-5.4.41.dist-info}/METADATA +57 -87
- {claude_mpm-5.0.9.dist-info → claude_mpm-5.4.41.dist-info}/RECORD +160 -211
- claude_mpm-5.4.41.dist-info/entry_points.txt +5 -0
- claude_mpm-5.4.41.dist-info/licenses/LICENSE +94 -0
- claude_mpm-5.4.41.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/hooks/claude_hooks/__pycache__/__init__.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-313.pyc +0 -0
- claude_mpm/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.0.9.dist-info/entry_points.txt +0 -10
- claude_mpm-5.0.9.dist-info/licenses/LICENSE +0 -21
- /claude_mpm/agents/{OUTPUT_STYLE.md → CLAUDE_MPM_OUTPUT_STYLE.md} +0 -0
- {claude_mpm-5.0.9.dist-info → claude_mpm-5.4.41.dist-info}/WHEEL +0 -0
- {claude_mpm-5.0.9.dist-info → claude_mpm-5.4.41.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,121 @@ 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
|
+
all_agent_files = [
|
|
549
|
+
f
|
|
550
|
+
for f in cache_dir.rglob("*.md")
|
|
551
|
+
if "/agents/" in str(f)
|
|
552
|
+
and str(f).count("/agents/") == 1
|
|
553
|
+
and f.stem.lower() != "base-agent"
|
|
554
|
+
]
|
|
555
|
+
|
|
556
|
+
# Build exclusion list for agents not in profile
|
|
557
|
+
excluded_agents = []
|
|
558
|
+
for agent_file in all_agent_files:
|
|
559
|
+
agent_name = agent_file.stem
|
|
560
|
+
if not profile_manager.is_agent_enabled(agent_name):
|
|
561
|
+
excluded_agents.append(agent_name)
|
|
562
|
+
|
|
563
|
+
if excluded_agents:
|
|
564
|
+
# Get singleton config and update with profile settings
|
|
565
|
+
# BUGFIX: Config is a singleton that ignores dict parameter if already initialized.
|
|
566
|
+
# Creating Config({...}) doesn't store excluded_agents - use set() instead.
|
|
567
|
+
deploy_config = Config()
|
|
568
|
+
deploy_config.set("agent_deployment.excluded_agents", excluded_agents)
|
|
569
|
+
deploy_config.set("agent_deployment.filter_non_mpm_agents", False)
|
|
570
|
+
deploy_config.set("agent_deployment.case_sensitive", False)
|
|
571
|
+
deploy_config.set("agent_deployment.exclude_dependencies", False)
|
|
572
|
+
logger.info(
|
|
573
|
+
f"Profile '{active_profile}': Excluding {len(excluded_agents)} agents from deployment"
|
|
574
|
+
)
|
|
575
|
+
|
|
576
|
+
deployment_service = AgentDeploymentService(config=deploy_config)
|
|
349
577
|
|
|
350
578
|
# Count agents in cache to show accurate progress
|
|
351
579
|
from pathlib import Path
|
|
352
580
|
|
|
353
|
-
cache_dir = Path.home() / ".claude-mpm" / "cache" / "
|
|
581
|
+
cache_dir = Path.home() / ".claude-mpm" / "cache" / "agents"
|
|
354
582
|
agent_count = 0
|
|
355
583
|
|
|
356
584
|
if cache_dir.exists():
|
|
357
|
-
#
|
|
358
|
-
#
|
|
359
|
-
#
|
|
585
|
+
# BUGFIX (cache-count-inflation): Clean up stale cache files
|
|
586
|
+
# from old repositories before counting to prevent inflated counts.
|
|
587
|
+
# Issue: Old caches like bobmatnyc/claude-mpm-agents/agents/
|
|
588
|
+
# were counted alongside current agents, inflating count
|
|
589
|
+
# from 44 to 85.
|
|
590
|
+
#
|
|
591
|
+
# Solution: Remove files with nested /agents/ paths
|
|
592
|
+
# (e.g., cache/agents/user/repo/agents/...)
|
|
593
|
+
# Keep only current agents (e.g., cache/agents/engineer/...)
|
|
594
|
+
removed_count = 0
|
|
595
|
+
stale_dirs = set()
|
|
596
|
+
|
|
597
|
+
for md_file in cache_dir.rglob("*.md"):
|
|
598
|
+
# Stale cache files have multiple /agents/ in their path
|
|
599
|
+
# Current: ~/.claude-mpm/cache/agents/engineer/...
|
|
600
|
+
# (1 occurrence)
|
|
601
|
+
# Old: ~/.claude-mpm/cache/agents/bobmatnyc/.../agents/...
|
|
602
|
+
# (2+ occurrences)
|
|
603
|
+
if str(md_file).count("/agents/") > 1:
|
|
604
|
+
# Track parent directory for cleanup
|
|
605
|
+
# Extract subdirectory under cache/agents/
|
|
606
|
+
# (e.g., "bobmatnyc")
|
|
607
|
+
parts = md_file.parts
|
|
608
|
+
cache_agents_idx = parts.index("agents")
|
|
609
|
+
if cache_agents_idx + 1 < len(parts):
|
|
610
|
+
stale_subdir = parts[cache_agents_idx + 1]
|
|
611
|
+
# Only remove if it's not a known category directory
|
|
612
|
+
if stale_subdir not in [
|
|
613
|
+
"engineer",
|
|
614
|
+
"ops",
|
|
615
|
+
"qa",
|
|
616
|
+
"universal",
|
|
617
|
+
"documentation",
|
|
618
|
+
"claude-mpm",
|
|
619
|
+
"security",
|
|
620
|
+
]:
|
|
621
|
+
stale_dirs.add(cache_dir / stale_subdir)
|
|
622
|
+
|
|
623
|
+
md_file.unlink()
|
|
624
|
+
removed_count += 1
|
|
625
|
+
|
|
626
|
+
# Remove empty stale directories
|
|
627
|
+
for stale_dir in stale_dirs:
|
|
628
|
+
if stale_dir.exists() and stale_dir.is_dir():
|
|
629
|
+
try:
|
|
630
|
+
# Remove directory and all contents
|
|
631
|
+
import shutil
|
|
632
|
+
|
|
633
|
+
shutil.rmtree(stale_dir)
|
|
634
|
+
except Exception:
|
|
635
|
+
pass # Ignore cleanup errors
|
|
636
|
+
|
|
637
|
+
if removed_count > 0:
|
|
638
|
+
from loguru import logger
|
|
639
|
+
|
|
640
|
+
logger.info(
|
|
641
|
+
f"Cleaned up {removed_count} stale cache files "
|
|
642
|
+
f"from old repositories"
|
|
643
|
+
)
|
|
644
|
+
|
|
645
|
+
# Count MD files in cache (agent markdown files from
|
|
646
|
+
# current repos)
|
|
647
|
+
# BUGFIX: Only count files in agent directories,
|
|
648
|
+
# not docs/templates/READMEs
|
|
649
|
+
# Valid agent paths must contain "/agents/" exactly ONCE
|
|
650
|
+
# (current structure)
|
|
360
651
|
# Exclude PM templates, BASE-AGENT, and documentation files
|
|
361
652
|
pm_templates = {
|
|
362
653
|
"base-agent.md",
|
|
@@ -379,61 +670,126 @@ def sync_remote_agents_on_startup():
|
|
|
379
670
|
"auto-deploy-index.md",
|
|
380
671
|
}
|
|
381
672
|
|
|
382
|
-
# Find all markdown files
|
|
673
|
+
# Find all markdown files (after cleanup)
|
|
383
674
|
all_md_files = list(cache_dir.rglob("*.md"))
|
|
384
675
|
|
|
385
676
|
# Filter to only agent files:
|
|
386
|
-
# 1. Must have "/agents/" in path
|
|
677
|
+
# 1. Must have "/agents/" in path exactly ONCE
|
|
678
|
+
# (current structure)
|
|
387
679
|
# 2. Must not be in PM templates or doc files
|
|
388
680
|
# 3. Exclude BASE-AGENT.md which is not a deployable agent
|
|
681
|
+
# 4. Exclude build artifacts (dist/, build/, .cache/)
|
|
682
|
+
# to prevent double-counting
|
|
389
683
|
agent_files = [
|
|
390
684
|
f
|
|
391
685
|
for f in all_md_files
|
|
392
686
|
if (
|
|
393
|
-
# Must be in an agent directory (from
|
|
687
|
+
# Must be in an agent directory (from current
|
|
688
|
+
# cache structure)
|
|
394
689
|
"/agents/" in str(f)
|
|
690
|
+
# NEW: Only ONE /agents/ in path (excludes old
|
|
691
|
+
# nested repos)
|
|
692
|
+
# This prevents counting old caches like
|
|
693
|
+
# bobmatnyc/claude-mpm-agents/agents/...
|
|
694
|
+
and str(f).count("/agents/") == 1
|
|
395
695
|
# Exclude PM templates, doc files, and BASE-AGENT
|
|
396
696
|
and f.name.lower() not in pm_templates
|
|
397
697
|
and f.name.lower() not in doc_files
|
|
398
698
|
and f.name.lower() != "base-agent.md"
|
|
699
|
+
# Exclude build artifacts (prevents double-counting
|
|
700
|
+
# source + built files)
|
|
701
|
+
and not any(
|
|
702
|
+
part in str(f).split("/")
|
|
703
|
+
for part in ["dist", "build", ".cache"]
|
|
704
|
+
)
|
|
399
705
|
)
|
|
400
706
|
]
|
|
401
707
|
agent_count = len(agent_files)
|
|
402
708
|
|
|
403
709
|
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"
|
|
710
|
+
# Deploy agents to project-level directory where Claude Code expects them
|
|
711
|
+
deploy_target = Path.cwd() / ".claude" / "agents"
|
|
414
712
|
deployment_result = deployment_service.deploy_agents(
|
|
415
713
|
target_dir=deploy_target,
|
|
416
714
|
force_rebuild=False, # Only deploy if versions differ
|
|
417
715
|
deployment_mode="update", # Version-aware updates
|
|
716
|
+
config=deploy_config, # Pass config to respect profile filtering
|
|
418
717
|
)
|
|
419
718
|
|
|
420
|
-
#
|
|
421
|
-
deploy_progress.update(agent_count)
|
|
422
|
-
|
|
423
|
-
# Finish deployment progress bar
|
|
719
|
+
# Get actual counts from deployment result (reflects configured agents)
|
|
424
720
|
deployed = len(deployment_result.get("deployed", []))
|
|
425
721
|
updated = len(deployment_result.get("updated", []))
|
|
426
722
|
skipped = len(deployment_result.get("skipped", []))
|
|
427
|
-
|
|
723
|
+
total_configured = deployed + updated + skipped
|
|
724
|
+
|
|
725
|
+
# FALLBACK: If deployment result doesn't track skipped agents (async path),
|
|
726
|
+
# count existing agents in target directory as "already deployed"
|
|
727
|
+
# This ensures accurate reporting when agents are already up-to-date
|
|
728
|
+
if total_configured == 0 and deploy_target.exists():
|
|
729
|
+
existing_agents = list(deploy_target.glob("*.md"))
|
|
730
|
+
# Filter out non-agent files (e.g., README.md, INSTRUCTIONS.md)
|
|
731
|
+
agent_count_in_target = len(
|
|
732
|
+
[
|
|
733
|
+
f
|
|
734
|
+
for f in existing_agents
|
|
735
|
+
if not f.name.startswith(("README", "INSTRUCTIONS"))
|
|
736
|
+
]
|
|
737
|
+
)
|
|
738
|
+
if agent_count_in_target > 0:
|
|
739
|
+
# All agents already deployed - count them as skipped
|
|
740
|
+
skipped = agent_count_in_target
|
|
741
|
+
total_configured = agent_count_in_target
|
|
428
742
|
|
|
429
|
-
#
|
|
743
|
+
# Create progress bar with actual configured agent count (not raw file count)
|
|
744
|
+
deploy_progress = ProgressBar(
|
|
745
|
+
total=total_configured if total_configured > 0 else 1,
|
|
746
|
+
prefix="Deploying agents",
|
|
747
|
+
show_percentage=True,
|
|
748
|
+
show_counter=True,
|
|
749
|
+
)
|
|
750
|
+
|
|
751
|
+
# Update progress bar to completion
|
|
752
|
+
deploy_progress.update(
|
|
753
|
+
total_configured if total_configured > 0 else 1
|
|
754
|
+
)
|
|
755
|
+
|
|
756
|
+
# Cleanup orphaned agents (ours but no longer deployed)
|
|
757
|
+
# Get list of deployed agent filenames (what should remain)
|
|
758
|
+
deployed_filenames = []
|
|
759
|
+
for agent_name in deployment_result.get("deployed", []):
|
|
760
|
+
deployed_filenames.append(f"{agent_name}.md")
|
|
761
|
+
for agent_name in deployment_result.get("updated", []):
|
|
762
|
+
deployed_filenames.append(f"{agent_name}.md")
|
|
763
|
+
for agent_name in deployment_result.get("skipped", []):
|
|
764
|
+
deployed_filenames.append(f"{agent_name}.md")
|
|
765
|
+
|
|
766
|
+
# Run cleanup and get count of removed agents
|
|
767
|
+
removed = _cleanup_orphaned_agents(
|
|
768
|
+
deploy_target, deployed_filenames
|
|
769
|
+
)
|
|
770
|
+
|
|
771
|
+
# Show total configured agents (deployed + updated + already existing)
|
|
772
|
+
# Include cache count for context and removed count if any
|
|
430
773
|
if deployed > 0 or updated > 0:
|
|
774
|
+
if removed > 0:
|
|
775
|
+
deploy_progress.finish(
|
|
776
|
+
f"Complete: {deployed} new, {updated} updated, {skipped} unchanged, "
|
|
777
|
+
f"{removed} removed ({total_configured} configured from {agent_count} files in cache)"
|
|
778
|
+
)
|
|
779
|
+
else:
|
|
780
|
+
deploy_progress.finish(
|
|
781
|
+
f"Complete: {deployed} new, {updated} updated, {skipped} unchanged "
|
|
782
|
+
f"({total_configured} configured from {agent_count} files in cache)"
|
|
783
|
+
)
|
|
784
|
+
elif removed > 0:
|
|
431
785
|
deploy_progress.finish(
|
|
432
|
-
f"Complete: {
|
|
786
|
+
f"Complete: {total_configured} agents deployed, "
|
|
787
|
+
f"{removed} removed ({agent_count} files in cache)"
|
|
433
788
|
)
|
|
434
789
|
else:
|
|
435
790
|
deploy_progress.finish(
|
|
436
|
-
f"Complete: {
|
|
791
|
+
f"Complete: {total_configured} agents deployed "
|
|
792
|
+
f"({agent_count} files in cache)"
|
|
437
793
|
)
|
|
438
794
|
|
|
439
795
|
# Display deployment errors to user (not just logs)
|
|
@@ -496,16 +852,48 @@ def sync_remote_skills_on_startup():
|
|
|
496
852
|
|
|
497
853
|
Workflow:
|
|
498
854
|
1. Sync all enabled Git sources (download/cache files) - Phase 1 progress bar
|
|
499
|
-
2.
|
|
500
|
-
3.
|
|
855
|
+
2. Scan deployed agents for skill requirements → save to configuration.yaml
|
|
856
|
+
3. Resolve which skills to deploy (user_defined vs agent_referenced)
|
|
857
|
+
4. Apply profile filtering if active
|
|
858
|
+
5. Deploy resolved skills to ~/.claude/skills/ - Phase 2 progress bar
|
|
859
|
+
6. Log deployment results with source indication
|
|
501
860
|
"""
|
|
502
861
|
try:
|
|
503
862
|
from pathlib import Path
|
|
504
863
|
|
|
505
864
|
from ..config.skill_sources import SkillSourceConfiguration
|
|
865
|
+
from ..core.shared.config_loader import ConfigLoader
|
|
866
|
+
from ..services.profile_manager import ProfileManager
|
|
506
867
|
from ..services.skills.git_skill_source_manager import GitSkillSourceManager
|
|
868
|
+
from ..services.skills.selective_skill_deployer import (
|
|
869
|
+
get_required_skills_from_agents,
|
|
870
|
+
get_skills_to_deploy,
|
|
871
|
+
save_agent_skills_to_config,
|
|
872
|
+
)
|
|
507
873
|
from ..utils.progress import ProgressBar
|
|
508
874
|
|
|
875
|
+
# Load active profile if configured
|
|
876
|
+
# Get project root (where .claude-mpm exists)
|
|
877
|
+
project_root = Path.cwd()
|
|
878
|
+
|
|
879
|
+
profile_manager = ProfileManager(project_dir=project_root)
|
|
880
|
+
config_loader = ConfigLoader()
|
|
881
|
+
main_config = config_loader.load_main_config()
|
|
882
|
+
active_profile = main_config.get("active_profile")
|
|
883
|
+
|
|
884
|
+
if active_profile:
|
|
885
|
+
success = profile_manager.load_profile(active_profile)
|
|
886
|
+
if success:
|
|
887
|
+
from ..core.logger import get_logger
|
|
888
|
+
|
|
889
|
+
logger = get_logger("cli")
|
|
890
|
+
summary = profile_manager.get_filtering_summary()
|
|
891
|
+
logger.info(
|
|
892
|
+
f"Profile '{active_profile}' active: "
|
|
893
|
+
f"{summary['enabled_skills_count']} skills enabled, "
|
|
894
|
+
f"{summary['disabled_patterns_count']} patterns disabled"
|
|
895
|
+
)
|
|
896
|
+
|
|
509
897
|
config = SkillSourceConfiguration()
|
|
510
898
|
manager = GitSkillSourceManager(config)
|
|
511
899
|
|
|
@@ -523,6 +911,8 @@ def sync_remote_skills_on_startup():
|
|
|
523
911
|
|
|
524
912
|
# Discover total file count across all sources
|
|
525
913
|
total_file_count = 0
|
|
914
|
+
total_skill_dirs = 0 # Count actual skill directories (folders with SKILL.md)
|
|
915
|
+
|
|
526
916
|
for source in enabled_sources:
|
|
527
917
|
try:
|
|
528
918
|
# Parse GitHub URL
|
|
@@ -546,15 +936,26 @@ def sync_remote_skills_on_startup():
|
|
|
546
936
|
]
|
|
547
937
|
total_file_count += len(relevant_files)
|
|
548
938
|
|
|
939
|
+
# Count skill directories (unique directories containing SKILL.md)
|
|
940
|
+
skill_dirs = set()
|
|
941
|
+
for f in all_files:
|
|
942
|
+
if f.endswith("/SKILL.md"):
|
|
943
|
+
# Extract directory path
|
|
944
|
+
skill_dir = "/".join(f.split("/")[:-1])
|
|
945
|
+
skill_dirs.add(skill_dir)
|
|
946
|
+
total_skill_dirs += len(skill_dirs)
|
|
947
|
+
|
|
549
948
|
except Exception as e:
|
|
550
949
|
logger.debug(f"Failed to discover files for {source.id}: {e}")
|
|
551
950
|
# Use estimate if discovery fails
|
|
552
951
|
total_file_count += 150
|
|
952
|
+
total_skill_dirs += 50 # Estimate ~50 skills
|
|
553
953
|
|
|
554
954
|
# Create progress bar for sync phase with actual file count
|
|
955
|
+
# Note: We sync files (md, json, etc.), but will deploy skill directories
|
|
555
956
|
sync_progress = ProgressBar(
|
|
556
957
|
total=total_file_count if total_file_count > 0 else 1,
|
|
557
|
-
prefix="Syncing
|
|
958
|
+
prefix="Syncing skill files",
|
|
558
959
|
show_percentage=True,
|
|
559
960
|
show_counter=True,
|
|
560
961
|
)
|
|
@@ -571,51 +972,142 @@ def sync_remote_skills_on_startup():
|
|
|
571
972
|
|
|
572
973
|
if cached > 0:
|
|
573
974
|
sync_progress.finish(
|
|
574
|
-
f"Complete: {downloaded} downloaded, {cached} cached ({total_files}
|
|
975
|
+
f"Complete: {downloaded} downloaded, {cached} cached ({total_files} files, {total_skill_dirs} skills)"
|
|
575
976
|
)
|
|
576
977
|
else:
|
|
577
978
|
# All new downloads (first sync)
|
|
578
|
-
sync_progress.finish(
|
|
979
|
+
sync_progress.finish(
|
|
980
|
+
f"Complete: {downloaded} files downloaded ({total_skill_dirs} skills)"
|
|
981
|
+
)
|
|
579
982
|
|
|
580
|
-
# Phase 2:
|
|
581
|
-
# This
|
|
582
|
-
# into flat deployment (e.g., collaboration-dispatching-parallel-agents/SKILL.md)
|
|
983
|
+
# Phase 2: Scan agents and save to configuration.yaml
|
|
984
|
+
# This step populates configuration.yaml with agent-referenced skills
|
|
583
985
|
if results["synced_count"] > 0:
|
|
584
|
-
|
|
986
|
+
agents_dir = Path.cwd() / ".claude" / "agents"
|
|
987
|
+
|
|
988
|
+
# Scan agents for skill requirements
|
|
989
|
+
agent_skills = get_required_skills_from_agents(agents_dir)
|
|
990
|
+
|
|
991
|
+
# Save to project-level configuration.yaml
|
|
992
|
+
project_config_path = Path.cwd() / ".claude-mpm" / "configuration.yaml"
|
|
993
|
+
save_agent_skills_to_config(list(agent_skills), project_config_path)
|
|
994
|
+
|
|
995
|
+
# Phase 3: Resolve which skills to deploy (user_defined or agent_referenced)
|
|
996
|
+
skills_to_deploy, skill_source = get_skills_to_deploy(project_config_path)
|
|
997
|
+
|
|
998
|
+
# Phase 4: Apply profile filtering if active
|
|
999
|
+
if active_profile and profile_manager.active_profile:
|
|
1000
|
+
# Filter skills based on profile
|
|
1001
|
+
if skills_to_deploy:
|
|
1002
|
+
# Filter the resolved skill list
|
|
1003
|
+
original_count = len(skills_to_deploy)
|
|
1004
|
+
filtered_skills = [
|
|
1005
|
+
skill
|
|
1006
|
+
for skill in skills_to_deploy
|
|
1007
|
+
if profile_manager.is_skill_enabled(skill)
|
|
1008
|
+
]
|
|
1009
|
+
filtered_count = original_count - len(filtered_skills)
|
|
1010
|
+
|
|
1011
|
+
# SAFEGUARD: Warn if all skills were filtered out (misconfiguration)
|
|
1012
|
+
if not filtered_skills and original_count > 0:
|
|
1013
|
+
logger.warning(
|
|
1014
|
+
f"Profile '{active_profile}' filtered ALL {original_count} skills. "
|
|
1015
|
+
f"This may indicate a naming mismatch in the profile."
|
|
1016
|
+
)
|
|
1017
|
+
elif filtered_count > 0:
|
|
1018
|
+
logger.info(
|
|
1019
|
+
f"Profile '{active_profile}' filtered {filtered_count} skills "
|
|
1020
|
+
f"({len(filtered_skills)} remaining)"
|
|
1021
|
+
)
|
|
1022
|
+
|
|
1023
|
+
skills_to_deploy = filtered_skills
|
|
1024
|
+
skill_source = f"{skill_source} + profile filtered"
|
|
1025
|
+
else:
|
|
1026
|
+
# No explicit skill list - filter from all available
|
|
1027
|
+
all_skills = manager.get_all_skills()
|
|
1028
|
+
filtered_skills = [
|
|
1029
|
+
skill["name"]
|
|
1030
|
+
for skill in all_skills
|
|
1031
|
+
if profile_manager.is_skill_enabled(skill["name"])
|
|
1032
|
+
]
|
|
1033
|
+
skills_to_deploy = filtered_skills
|
|
1034
|
+
skill_source = "profile filtered"
|
|
1035
|
+
logger.info(
|
|
1036
|
+
f"Profile '{active_profile}': "
|
|
1037
|
+
f"{len(filtered_skills)} skills enabled from {len(all_skills)} available"
|
|
1038
|
+
)
|
|
1039
|
+
|
|
1040
|
+
# Get all skills to determine counts
|
|
585
1041
|
all_skills = manager.get_all_skills()
|
|
586
|
-
|
|
1042
|
+
total_skill_count = len(all_skills)
|
|
587
1043
|
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
prefix="Deploying skill directories",
|
|
593
|
-
show_percentage=True,
|
|
594
|
-
show_counter=True,
|
|
595
|
-
)
|
|
1044
|
+
# Determine skill count based on resolution
|
|
1045
|
+
skill_count = (
|
|
1046
|
+
len(skills_to_deploy) if skills_to_deploy else total_skill_count
|
|
1047
|
+
)
|
|
596
1048
|
|
|
597
|
-
|
|
1049
|
+
if skill_count > 0:
|
|
1050
|
+
# Deploy skills with resolved filter
|
|
598
1051
|
# Deploy to project directory (like agents), not user directory
|
|
599
1052
|
deployment_result = manager.deploy_skills(
|
|
600
1053
|
target_dir=Path.cwd() / ".claude" / "skills",
|
|
601
1054
|
force=False,
|
|
602
|
-
|
|
1055
|
+
skill_filter=set(skills_to_deploy) if skills_to_deploy else None,
|
|
603
1056
|
)
|
|
604
1057
|
|
|
605
|
-
#
|
|
1058
|
+
# Get actual counts from deployment result
|
|
606
1059
|
deployed = deployment_result.get("deployed_count", 0)
|
|
607
1060
|
skipped = deployment_result.get("skipped_count", 0)
|
|
1061
|
+
filtered = deployment_result.get("filtered_count", 0)
|
|
608
1062
|
total_available = deployed + skipped
|
|
609
1063
|
|
|
1064
|
+
# Only show progress bar if there are skills to deploy
|
|
1065
|
+
if total_available > 0:
|
|
1066
|
+
deploy_progress = ProgressBar(
|
|
1067
|
+
total=total_available,
|
|
1068
|
+
prefix="Deploying skill directories",
|
|
1069
|
+
show_percentage=True,
|
|
1070
|
+
show_counter=True,
|
|
1071
|
+
)
|
|
1072
|
+
# Update progress bar to completion
|
|
1073
|
+
deploy_progress.update(total_available)
|
|
1074
|
+
else:
|
|
1075
|
+
# No skills to deploy - create dummy progress for message only
|
|
1076
|
+
deploy_progress = ProgressBar(
|
|
1077
|
+
total=1,
|
|
1078
|
+
prefix="Deploying skill directories",
|
|
1079
|
+
show_percentage=False,
|
|
1080
|
+
show_counter=False,
|
|
1081
|
+
)
|
|
1082
|
+
deploy_progress.update(1)
|
|
1083
|
+
|
|
610
1084
|
# Show total available skills (deployed + already existing)
|
|
611
|
-
#
|
|
1085
|
+
# Include source indication (user_defined vs agent_referenced)
|
|
1086
|
+
# Note: total_skill_count is from cache, total_available is what's deployed/needed
|
|
1087
|
+
source_label = (
|
|
1088
|
+
"user override" if skill_source == "user_defined" else "from agents"
|
|
1089
|
+
)
|
|
1090
|
+
|
|
612
1091
|
if deployed > 0:
|
|
1092
|
+
if filtered > 0:
|
|
1093
|
+
deploy_progress.finish(
|
|
1094
|
+
f"Complete: {deployed} new, {skipped} unchanged "
|
|
1095
|
+
f"({total_available} {source_label}, {filtered} files in cache)"
|
|
1096
|
+
)
|
|
1097
|
+
else:
|
|
1098
|
+
deploy_progress.finish(
|
|
1099
|
+
f"Complete: {deployed} new, {skipped} unchanged "
|
|
1100
|
+
f"({total_available} skills {source_label} from {total_skill_count} files in cache)"
|
|
1101
|
+
)
|
|
1102
|
+
elif filtered > 0:
|
|
1103
|
+
# Skills filtered means agents require fewer skills than available
|
|
613
1104
|
deploy_progress.finish(
|
|
614
|
-
f"
|
|
1105
|
+
f"No skills needed ({source_label}, {total_skill_count} files in cache)"
|
|
615
1106
|
)
|
|
616
1107
|
else:
|
|
617
1108
|
deploy_progress.finish(
|
|
618
|
-
f"Complete: {total_available} skills
|
|
1109
|
+
f"Complete: {total_available} skills {source_label} "
|
|
1110
|
+
f"({total_skill_count} files in cache)"
|
|
619
1111
|
)
|
|
620
1112
|
|
|
621
1113
|
# Log deployment errors if any
|
|
@@ -644,6 +1136,241 @@ def sync_remote_skills_on_startup():
|
|
|
644
1136
|
# Continue execution - skill sync failure shouldn't block startup
|
|
645
1137
|
|
|
646
1138
|
|
|
1139
|
+
def show_agent_summary():
|
|
1140
|
+
"""
|
|
1141
|
+
Display agent availability summary on startup.
|
|
1142
|
+
|
|
1143
|
+
WHY: Users should see at a glance how many agents are available and installed
|
|
1144
|
+
without having to run /mpm-agents list.
|
|
1145
|
+
|
|
1146
|
+
DESIGN DECISION: Fast, non-blocking check that counts agents from the deployment
|
|
1147
|
+
directory. Shows simple "X installed / Y available" format. Failures are silent
|
|
1148
|
+
to avoid blocking startup.
|
|
1149
|
+
"""
|
|
1150
|
+
try:
|
|
1151
|
+
from pathlib import Path
|
|
1152
|
+
|
|
1153
|
+
# Count deployed agents (installed)
|
|
1154
|
+
deploy_target = Path.cwd() / ".claude" / "agents"
|
|
1155
|
+
installed_count = 0
|
|
1156
|
+
if deploy_target.exists():
|
|
1157
|
+
# Count .md files, excluding README and other docs
|
|
1158
|
+
agent_files = [
|
|
1159
|
+
f
|
|
1160
|
+
for f in deploy_target.glob("*.md")
|
|
1161
|
+
if not f.name.startswith(("README", "INSTRUCTIONS", "."))
|
|
1162
|
+
]
|
|
1163
|
+
installed_count = len(agent_files)
|
|
1164
|
+
|
|
1165
|
+
# Count available agents in cache (from remote sources)
|
|
1166
|
+
cache_dir = Path.home() / ".claude-mpm" / "cache" / "agents"
|
|
1167
|
+
available_count = 0
|
|
1168
|
+
if cache_dir.exists():
|
|
1169
|
+
# Use same filtering logic as agent deployment (lines 486-533 in startup.py)
|
|
1170
|
+
pm_templates = {
|
|
1171
|
+
"base-agent.md",
|
|
1172
|
+
"circuit_breakers.md",
|
|
1173
|
+
"pm_examples.md",
|
|
1174
|
+
"pm_red_flags.md",
|
|
1175
|
+
"research_gate_examples.md",
|
|
1176
|
+
"response_format.md",
|
|
1177
|
+
"ticket_completeness_examples.md",
|
|
1178
|
+
"validation_templates.md",
|
|
1179
|
+
"git_file_tracking.md",
|
|
1180
|
+
}
|
|
1181
|
+
doc_files = {
|
|
1182
|
+
"readme.md",
|
|
1183
|
+
"changelog.md",
|
|
1184
|
+
"contributing.md",
|
|
1185
|
+
"implementation-summary.md",
|
|
1186
|
+
"reorganization-plan.md",
|
|
1187
|
+
"auto-deploy-index.md",
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
# Find all markdown files in agents/ directories
|
|
1191
|
+
all_md_files = list(cache_dir.rglob("*.md"))
|
|
1192
|
+
agent_files = [
|
|
1193
|
+
f
|
|
1194
|
+
for f in all_md_files
|
|
1195
|
+
if (
|
|
1196
|
+
"/agents/" in str(f)
|
|
1197
|
+
and f.name.lower() not in pm_templates
|
|
1198
|
+
and f.name.lower() not in doc_files
|
|
1199
|
+
and f.name.lower() != "base-agent.md"
|
|
1200
|
+
and not any(
|
|
1201
|
+
part in str(f).split("/")
|
|
1202
|
+
for part in ["dist", "build", ".cache"]
|
|
1203
|
+
)
|
|
1204
|
+
)
|
|
1205
|
+
]
|
|
1206
|
+
available_count = len(agent_files)
|
|
1207
|
+
|
|
1208
|
+
# Display summary if we have agents
|
|
1209
|
+
if installed_count > 0 or available_count > 0:
|
|
1210
|
+
print(
|
|
1211
|
+
f"✓ Agents: {installed_count} deployed / {available_count} cached",
|
|
1212
|
+
flush=True,
|
|
1213
|
+
)
|
|
1214
|
+
|
|
1215
|
+
except Exception as e:
|
|
1216
|
+
# Silent failure - agent summary is informational only
|
|
1217
|
+
from ..core.logger import get_logger
|
|
1218
|
+
|
|
1219
|
+
logger = get_logger("cli")
|
|
1220
|
+
logger.debug(f"Failed to generate agent summary: {e}")
|
|
1221
|
+
|
|
1222
|
+
|
|
1223
|
+
def show_skill_summary():
|
|
1224
|
+
"""
|
|
1225
|
+
Display skill availability summary on startup.
|
|
1226
|
+
|
|
1227
|
+
WHY: Users should see at a glance how many skills are deployed and available
|
|
1228
|
+
from collections, similar to the agent summary.
|
|
1229
|
+
|
|
1230
|
+
DESIGN DECISION: Fast, non-blocking check that counts skills from deployment
|
|
1231
|
+
directory and collection repos. Shows "X installed (Y available)" format.
|
|
1232
|
+
Failures are silent to avoid blocking startup.
|
|
1233
|
+
"""
|
|
1234
|
+
try:
|
|
1235
|
+
from pathlib import Path
|
|
1236
|
+
|
|
1237
|
+
# Count deployed skills (installed)
|
|
1238
|
+
skills_dir = Path.home() / ".claude" / "skills"
|
|
1239
|
+
installed_count = 0
|
|
1240
|
+
if skills_dir.exists():
|
|
1241
|
+
# Count directories with SKILL.md (excludes collection repos)
|
|
1242
|
+
# Exclude collection directories (obra-superpowers, etc.)
|
|
1243
|
+
skill_dirs = [
|
|
1244
|
+
d
|
|
1245
|
+
for d in skills_dir.iterdir()
|
|
1246
|
+
if d.is_dir()
|
|
1247
|
+
and (d / "SKILL.md").exists()
|
|
1248
|
+
and not (d / ".git").exists() # Exclude collection repos
|
|
1249
|
+
]
|
|
1250
|
+
installed_count = len(skill_dirs)
|
|
1251
|
+
|
|
1252
|
+
# Count available skills in collections
|
|
1253
|
+
available_count = 0
|
|
1254
|
+
if skills_dir.exists():
|
|
1255
|
+
# Scan all collection directories (those with .git)
|
|
1256
|
+
for collection_dir in skills_dir.iterdir():
|
|
1257
|
+
if (
|
|
1258
|
+
not collection_dir.is_dir()
|
|
1259
|
+
or not (collection_dir / ".git").exists()
|
|
1260
|
+
):
|
|
1261
|
+
continue
|
|
1262
|
+
|
|
1263
|
+
# Count skill directories in this collection
|
|
1264
|
+
# Skills can be nested in: skills/category/skill-name/SKILL.md
|
|
1265
|
+
# or in flat structure: skill-name/SKILL.md
|
|
1266
|
+
for root, dirs, files in os.walk(collection_dir):
|
|
1267
|
+
if "SKILL.md" in files:
|
|
1268
|
+
# Exclude build artifacts and hidden directories (within the collection)
|
|
1269
|
+
# Get relative path from collection_dir to avoid excluding based on .claude parent
|
|
1270
|
+
root_path = Path(root)
|
|
1271
|
+
relative_parts = root_path.relative_to(collection_dir).parts
|
|
1272
|
+
if not any(
|
|
1273
|
+
part.startswith(".")
|
|
1274
|
+
or part in ["dist", "build", "__pycache__"]
|
|
1275
|
+
for part in relative_parts
|
|
1276
|
+
):
|
|
1277
|
+
available_count += 1
|
|
1278
|
+
|
|
1279
|
+
# Display summary if we have skills
|
|
1280
|
+
if installed_count > 0 or available_count > 0:
|
|
1281
|
+
print(
|
|
1282
|
+
f"✓ Skills: {installed_count} installed ({available_count} available)",
|
|
1283
|
+
flush=True,
|
|
1284
|
+
)
|
|
1285
|
+
|
|
1286
|
+
except Exception as e:
|
|
1287
|
+
# Silent failure - skill summary is informational only
|
|
1288
|
+
from ..core.logger import get_logger
|
|
1289
|
+
|
|
1290
|
+
logger = get_logger("cli")
|
|
1291
|
+
logger.debug(f"Failed to generate skill summary: {e}")
|
|
1292
|
+
|
|
1293
|
+
|
|
1294
|
+
def verify_and_show_pm_skills():
|
|
1295
|
+
"""Verify PM skills and display status.
|
|
1296
|
+
|
|
1297
|
+
WHY: PM skills are essential for PM agent operation.
|
|
1298
|
+
Shows deployment status and auto-deploys if missing.
|
|
1299
|
+
"""
|
|
1300
|
+
try:
|
|
1301
|
+
from pathlib import Path
|
|
1302
|
+
|
|
1303
|
+
from ..services.pm_skills_deployer import PMSkillsDeployerService
|
|
1304
|
+
|
|
1305
|
+
deployer = PMSkillsDeployerService()
|
|
1306
|
+
project_dir = Path.cwd()
|
|
1307
|
+
|
|
1308
|
+
result = deployer.verify_pm_skills(project_dir)
|
|
1309
|
+
|
|
1310
|
+
if result.verified:
|
|
1311
|
+
# Show verified status
|
|
1312
|
+
print(f"✓ PM skills: {result.skill_count} verified", flush=True)
|
|
1313
|
+
else:
|
|
1314
|
+
# Auto-deploy if missing
|
|
1315
|
+
print("Deploying PM skills...", end="", flush=True)
|
|
1316
|
+
deploy_result = deployer.deploy_pm_skills(project_dir)
|
|
1317
|
+
if deploy_result.success:
|
|
1318
|
+
total = len(deploy_result.deployed) + len(deploy_result.skipped)
|
|
1319
|
+
print(f"\r✓ PM skills: {total} deployed" + " " * 20, flush=True)
|
|
1320
|
+
else:
|
|
1321
|
+
print(f"\r⚠ PM skills: deployment failed" + " " * 20, flush=True)
|
|
1322
|
+
|
|
1323
|
+
except ImportError:
|
|
1324
|
+
# PM skills deployer not available - skip silently
|
|
1325
|
+
pass
|
|
1326
|
+
except Exception as e:
|
|
1327
|
+
from ..core.logger import get_logger
|
|
1328
|
+
|
|
1329
|
+
logger = get_logger("cli")
|
|
1330
|
+
logger.debug(f"PM skills verification failed: {e}")
|
|
1331
|
+
|
|
1332
|
+
|
|
1333
|
+
def auto_install_chrome_devtools_on_startup():
|
|
1334
|
+
"""
|
|
1335
|
+
Automatically install chrome-devtools-mcp on startup if enabled.
|
|
1336
|
+
|
|
1337
|
+
WHY: Browser automation capabilities should be available out-of-the-box without
|
|
1338
|
+
manual MCP server configuration. chrome-devtools-mcp provides powerful browser
|
|
1339
|
+
interaction tools for Claude Code.
|
|
1340
|
+
|
|
1341
|
+
DESIGN DECISION: Non-blocking installation that doesn't prevent startup if it fails.
|
|
1342
|
+
Respects user configuration setting (enabled by default). Only installs if not
|
|
1343
|
+
already configured in Claude.
|
|
1344
|
+
"""
|
|
1345
|
+
try:
|
|
1346
|
+
# Check if auto-install is disabled in config
|
|
1347
|
+
from ..config.config_loader import ConfigLoader
|
|
1348
|
+
|
|
1349
|
+
config_loader = ConfigLoader()
|
|
1350
|
+
try:
|
|
1351
|
+
config = config_loader.load_main_config()
|
|
1352
|
+
chrome_devtools_config = config.get("chrome_devtools", {})
|
|
1353
|
+
if not chrome_devtools_config.get("auto_install", True):
|
|
1354
|
+
# Auto-install disabled, skip silently
|
|
1355
|
+
return
|
|
1356
|
+
except Exception:
|
|
1357
|
+
# If config loading fails, assume auto-install is enabled (default)
|
|
1358
|
+
pass
|
|
1359
|
+
|
|
1360
|
+
# Import and run chrome-devtools installation
|
|
1361
|
+
from ..cli.chrome_devtools_installer import auto_install_chrome_devtools
|
|
1362
|
+
|
|
1363
|
+
auto_install_chrome_devtools(quiet=False)
|
|
1364
|
+
|
|
1365
|
+
except Exception as e:
|
|
1366
|
+
# Import logger here to avoid circular imports
|
|
1367
|
+
from ..core.logger import get_logger
|
|
1368
|
+
|
|
1369
|
+
logger = get_logger("cli")
|
|
1370
|
+
logger.debug(f"Failed to auto-install chrome-devtools-mcp: {e}")
|
|
1371
|
+
# Continue execution - chrome-devtools installation failure shouldn't block startup
|
|
1372
|
+
|
|
1373
|
+
|
|
647
1374
|
def run_background_services():
|
|
648
1375
|
"""
|
|
649
1376
|
Initialize all background services on startup.
|
|
@@ -656,11 +1383,17 @@ def run_background_services():
|
|
|
656
1383
|
file creation in project .claude/ directories.
|
|
657
1384
|
See: SystemInstructionsDeployer and agent_deployment.py line 504-509
|
|
658
1385
|
"""
|
|
1386
|
+
# Sync hooks early to ensure up-to-date configuration
|
|
1387
|
+
# RATIONALE: Hooks should be synced before other services to fix stale configs
|
|
1388
|
+
# This is fast (<100ms) and non-blocking, so it doesn't delay startup
|
|
1389
|
+
sync_hooks_on_startup() # Shows "Syncing Claude Code hooks... ✓"
|
|
1390
|
+
|
|
659
1391
|
initialize_project_registry()
|
|
660
1392
|
check_mcp_auto_configuration()
|
|
661
1393
|
verify_mcp_gateway_startup()
|
|
662
1394
|
check_for_updates_async()
|
|
663
1395
|
sync_remote_agents_on_startup() # Sync agents from remote sources
|
|
1396
|
+
show_agent_summary() # Display agent counts after deployment
|
|
664
1397
|
|
|
665
1398
|
# Skills deployment order (precedence: remote > bundled)
|
|
666
1399
|
# 1. Deploy bundled skills first (base layer from package)
|
|
@@ -670,9 +1403,14 @@ def run_background_services():
|
|
|
670
1403
|
deploy_bundled_skills() # Base layer: package-bundled skills
|
|
671
1404
|
sync_remote_skills_on_startup() # Override layer: Git-based skills (takes precedence)
|
|
672
1405
|
discover_and_link_runtime_skills() # Discovery: user-added skills
|
|
1406
|
+
show_skill_summary() # Display skill counts after deployment
|
|
1407
|
+
verify_and_show_pm_skills() # PM skills verification and status
|
|
673
1408
|
|
|
674
1409
|
deploy_output_style_on_startup()
|
|
675
1410
|
|
|
1411
|
+
# Auto-install chrome-devtools-mcp for browser automation
|
|
1412
|
+
auto_install_chrome_devtools_on_startup()
|
|
1413
|
+
|
|
676
1414
|
|
|
677
1415
|
def setup_mcp_server_logging(args):
|
|
678
1416
|
"""
|
|
@@ -800,18 +1538,10 @@ def verify_mcp_gateway_startup():
|
|
|
800
1538
|
DESIGN DECISION: This is non-blocking - failures are logged but don't prevent
|
|
801
1539
|
startup to ensure claude-mpm remains functional even if MCP gateway has issues.
|
|
802
1540
|
"""
|
|
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
|
|
1541
|
+
# DISABLED: MCP service verification removed - Claude Code handles MCP natively
|
|
1542
|
+
# The previous check warned about missing MCP services, but users should configure
|
|
1543
|
+
# MCP servers through Claude Code's native MCP management, not through claude-mpm.
|
|
1544
|
+
# See: https://docs.anthropic.com/en/docs/claude-code/mcp
|
|
815
1545
|
|
|
816
1546
|
try:
|
|
817
1547
|
import asyncio
|