claude-mpm 5.4.41__py3-none-any.whl → 5.6.72__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of claude-mpm might be problematic. Click here for more details.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/CLAUDE_MPM_OUTPUT_STYLE.md +66 -241
- claude_mpm/agents/CLAUDE_MPM_RESEARCH_OUTPUT_STYLE.md +413 -0
- claude_mpm/agents/CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md +109 -1925
- claude_mpm/agents/PM_INSTRUCTIONS.md +161 -298
- claude_mpm/agents/WORKFLOW.md +2 -0
- claude_mpm/agents/templates/circuit-breakers.md +26 -17
- claude_mpm/auth/__init__.py +35 -0
- claude_mpm/auth/callback_server.py +328 -0
- claude_mpm/auth/models.py +104 -0
- claude_mpm/auth/oauth_manager.py +266 -0
- claude_mpm/auth/providers/__init__.py +12 -0
- claude_mpm/auth/providers/base.py +165 -0
- claude_mpm/auth/providers/google.py +261 -0
- claude_mpm/auth/token_storage.py +252 -0
- claude_mpm/cli/__init__.py +5 -1
- claude_mpm/cli/commands/agents.py +2 -4
- claude_mpm/cli/commands/agents_reconcile.py +197 -0
- claude_mpm/cli/commands/autotodos.py +566 -0
- claude_mpm/cli/commands/commander.py +216 -0
- claude_mpm/cli/commands/configure.py +620 -21
- claude_mpm/cli/commands/configure_agent_display.py +3 -1
- claude_mpm/cli/commands/hook_errors.py +60 -60
- claude_mpm/cli/commands/mcp.py +29 -17
- claude_mpm/cli/commands/mcp_command_router.py +39 -0
- claude_mpm/cli/commands/mcp_service_commands.py +304 -0
- claude_mpm/cli/commands/monitor.py +2 -2
- claude_mpm/cli/commands/mpm_init/core.py +15 -8
- claude_mpm/cli/commands/oauth.py +481 -0
- claude_mpm/cli/commands/profile.py +9 -10
- claude_mpm/cli/commands/run.py +35 -3
- claude_mpm/cli/commands/skill_source.py +51 -2
- claude_mpm/cli/commands/skills.py +182 -32
- claude_mpm/cli/executor.py +129 -16
- claude_mpm/cli/helpers.py +1 -1
- claude_mpm/cli/interactive/__init__.py +10 -0
- claude_mpm/cli/interactive/agent_wizard.py +30 -50
- claude_mpm/cli/interactive/questionary_styles.py +65 -0
- claude_mpm/cli/interactive/skill_selector.py +481 -0
- claude_mpm/cli/parsers/base_parser.py +89 -1
- claude_mpm/cli/parsers/commander_parser.py +116 -0
- claude_mpm/cli/parsers/mcp_parser.py +79 -0
- claude_mpm/cli/parsers/oauth_parser.py +165 -0
- claude_mpm/cli/parsers/profile_parser.py +0 -1
- claude_mpm/cli/parsers/run_parser.py +10 -0
- claude_mpm/cli/parsers/skill_source_parser.py +4 -0
- claude_mpm/cli/parsers/skills_parser.py +2 -3
- claude_mpm/cli/startup.py +662 -524
- claude_mpm/cli/startup_display.py +76 -7
- claude_mpm/cli/startup_logging.py +2 -2
- claude_mpm/cli/utils.py +7 -3
- claude_mpm/commander/__init__.py +78 -0
- claude_mpm/commander/adapters/__init__.py +60 -0
- claude_mpm/commander/adapters/auggie.py +260 -0
- claude_mpm/commander/adapters/base.py +288 -0
- claude_mpm/commander/adapters/claude_code.py +392 -0
- claude_mpm/commander/adapters/codex.py +237 -0
- claude_mpm/commander/adapters/communication.py +366 -0
- claude_mpm/commander/adapters/example_usage.py +310 -0
- claude_mpm/commander/adapters/mpm.py +389 -0
- claude_mpm/commander/adapters/registry.py +204 -0
- claude_mpm/commander/api/__init__.py +16 -0
- claude_mpm/commander/api/app.py +121 -0
- claude_mpm/commander/api/errors.py +133 -0
- claude_mpm/commander/api/routes/__init__.py +8 -0
- claude_mpm/commander/api/routes/events.py +184 -0
- claude_mpm/commander/api/routes/inbox.py +171 -0
- claude_mpm/commander/api/routes/messages.py +148 -0
- claude_mpm/commander/api/routes/projects.py +271 -0
- claude_mpm/commander/api/routes/sessions.py +226 -0
- claude_mpm/commander/api/routes/work.py +296 -0
- claude_mpm/commander/api/schemas.py +186 -0
- claude_mpm/commander/chat/__init__.py +7 -0
- claude_mpm/commander/chat/cli.py +149 -0
- claude_mpm/commander/chat/commands.py +122 -0
- claude_mpm/commander/chat/repl.py +1821 -0
- claude_mpm/commander/config.py +51 -0
- claude_mpm/commander/config_loader.py +115 -0
- claude_mpm/commander/core/__init__.py +10 -0
- claude_mpm/commander/core/block_manager.py +325 -0
- claude_mpm/commander/core/response_manager.py +323 -0
- claude_mpm/commander/daemon.py +603 -0
- claude_mpm/commander/env_loader.py +59 -0
- claude_mpm/commander/events/__init__.py +26 -0
- claude_mpm/commander/events/manager.py +392 -0
- claude_mpm/commander/frameworks/__init__.py +12 -0
- claude_mpm/commander/frameworks/base.py +233 -0
- claude_mpm/commander/frameworks/claude_code.py +58 -0
- claude_mpm/commander/frameworks/mpm.py +57 -0
- claude_mpm/commander/git/__init__.py +5 -0
- claude_mpm/commander/git/worktree_manager.py +212 -0
- claude_mpm/commander/inbox/__init__.py +16 -0
- claude_mpm/commander/inbox/dedup.py +128 -0
- claude_mpm/commander/inbox/inbox.py +224 -0
- claude_mpm/commander/inbox/models.py +70 -0
- claude_mpm/commander/instance_manager.py +865 -0
- claude_mpm/commander/llm/__init__.py +6 -0
- claude_mpm/commander/llm/openrouter_client.py +167 -0
- claude_mpm/commander/llm/summarizer.py +70 -0
- claude_mpm/commander/memory/__init__.py +45 -0
- claude_mpm/commander/memory/compression.py +347 -0
- claude_mpm/commander/memory/embeddings.py +230 -0
- claude_mpm/commander/memory/entities.py +310 -0
- claude_mpm/commander/memory/example_usage.py +290 -0
- claude_mpm/commander/memory/integration.py +325 -0
- claude_mpm/commander/memory/search.py +381 -0
- claude_mpm/commander/memory/store.py +657 -0
- claude_mpm/commander/models/__init__.py +18 -0
- claude_mpm/commander/models/events.py +127 -0
- claude_mpm/commander/models/project.py +162 -0
- claude_mpm/commander/models/work.py +214 -0
- claude_mpm/commander/parsing/__init__.py +20 -0
- claude_mpm/commander/parsing/extractor.py +132 -0
- claude_mpm/commander/parsing/output_parser.py +270 -0
- claude_mpm/commander/parsing/patterns.py +100 -0
- claude_mpm/commander/persistence/__init__.py +11 -0
- claude_mpm/commander/persistence/event_store.py +274 -0
- claude_mpm/commander/persistence/state_store.py +403 -0
- claude_mpm/commander/persistence/work_store.py +164 -0
- claude_mpm/commander/polling/__init__.py +13 -0
- claude_mpm/commander/polling/event_detector.py +104 -0
- claude_mpm/commander/polling/output_buffer.py +49 -0
- claude_mpm/commander/polling/output_poller.py +153 -0
- claude_mpm/commander/project_session.py +268 -0
- claude_mpm/commander/proxy/__init__.py +12 -0
- claude_mpm/commander/proxy/formatter.py +89 -0
- claude_mpm/commander/proxy/output_handler.py +191 -0
- claude_mpm/commander/proxy/relay.py +155 -0
- claude_mpm/commander/registry.py +410 -0
- claude_mpm/commander/runtime/__init__.py +10 -0
- claude_mpm/commander/runtime/executor.py +191 -0
- claude_mpm/commander/runtime/monitor.py +346 -0
- claude_mpm/commander/session/__init__.py +6 -0
- claude_mpm/commander/session/context.py +81 -0
- claude_mpm/commander/session/manager.py +59 -0
- claude_mpm/commander/tmux_orchestrator.py +362 -0
- claude_mpm/commander/web/__init__.py +1 -0
- claude_mpm/commander/work/__init__.py +30 -0
- claude_mpm/commander/work/executor.py +207 -0
- claude_mpm/commander/work/queue.py +405 -0
- claude_mpm/commander/workflow/__init__.py +27 -0
- claude_mpm/commander/workflow/event_handler.py +241 -0
- claude_mpm/commander/workflow/notifier.py +146 -0
- claude_mpm/commands/mpm-config.md +8 -0
- claude_mpm/commands/mpm-doctor.md +8 -0
- claude_mpm/commands/mpm-help.md +8 -0
- claude_mpm/commands/mpm-init.md +8 -0
- claude_mpm/commands/mpm-monitor.md +8 -0
- claude_mpm/commands/mpm-organize.md +8 -0
- claude_mpm/commands/mpm-postmortem.md +8 -0
- claude_mpm/commands/mpm-session-resume.md +9 -1
- claude_mpm/commands/mpm-status.md +8 -0
- claude_mpm/commands/mpm-ticket-view.md +8 -0
- claude_mpm/commands/mpm-version.md +8 -0
- claude_mpm/commands/mpm.md +8 -0
- claude_mpm/config/agent_presets.py +8 -7
- claude_mpm/config/skill_sources.py +16 -0
- claude_mpm/constants.py +6 -0
- claude_mpm/core/claude_runner.py +154 -2
- claude_mpm/core/config.py +35 -22
- claude_mpm/core/config_constants.py +74 -9
- claude_mpm/core/constants.py +56 -12
- claude_mpm/core/hook_manager.py +53 -4
- claude_mpm/core/interactive_session.py +12 -11
- claude_mpm/core/logger.py +26 -9
- claude_mpm/core/logging_utils.py +39 -13
- claude_mpm/core/network_config.py +148 -0
- claude_mpm/core/oneshot_session.py +7 -6
- claude_mpm/core/optimized_startup.py +3 -1
- claude_mpm/core/output_style_manager.py +66 -18
- claude_mpm/core/shared/config_loader.py +3 -1
- claude_mpm/core/socketio_pool.py +47 -15
- claude_mpm/core/unified_config.py +54 -8
- claude_mpm/core/unified_paths.py +95 -90
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.C33zOoyM.css +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.CW1J-YuA.css +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/1WZnGYqX.js +24 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/67pF3qNn.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/6RxdMKe4.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/8cZrfX0h.js +60 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/9a6T2nm-.js +7 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/B443AUzu.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/B8AwtY2H.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BF15LAsF.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BQaXIfA_.js +331 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BRcwIQNr.js +4 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{uj46x2Wr.js → BSNlmTZj.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BV6nKitt.js +43 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BViJ8lZt.js +128 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BcQ-Q0FE.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Bpyvgze_.js +30 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BzTRqg-z.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C0Fr8dve.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C3rbW_a-.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C8WYN38h.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C9I8FlXH.js +61 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CIQcWgO2.js +36 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CIctN7YN.js +7 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CKrS_JZW.js +145 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CR6P9C4A.js +89 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CRRR9MD_.js +2 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CRcR2DqT.js +334 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CSXtMOf0.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CT-sbxSk.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CWm6DJsp.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CmKTTxBW.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CpqQ1Kzn.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Cu_Erd72.js +261 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/D2nGpDRe.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/D9iCMida.js +267 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/D9ykgMoY.js +10 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DL2Ldur1.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DPfltzjH.js +165 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{N4qtv3Hx.js → DR8nis88.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DUliQN2b.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DVp1hx9R.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DXlhR01x.js +122 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/D_lyTybS.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DngoTTgh.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DqkmHtDC.js +220 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DsDh8EYs.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DypDmXgd.js +139 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Gi6I4Gst.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/IPYC-LnN.js +162 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/JTLiF7dt.js +24 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/JpevfAFt.js +68 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DjhvlsAc.js → NqQ1dWOy.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/R8CEIRAd.js +2 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Zxy7qc-l.js +64 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/q9Hm6zAU.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/qtd3IeO4.js +15 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/ulBFON_C.js +65 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/wQVh1CoA.js +10 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/app.Dr7t0z2J.js +2 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.BGhZHUS3.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/{0.CAGBuiOw.js → 0.RgBboRvH.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/1.DG-KkbDf.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.D_jnf-x6.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/version.json +1 -1
- claude_mpm/dashboard/static/svelte-build/index.html +11 -11
- claude_mpm/dashboard-svelte/node_modules/katex/src/fonts/generate_fonts.py +58 -0
- claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/extract_tfms.py +114 -0
- claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/extract_ttfs.py +122 -0
- claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/format_json.py +28 -0
- claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/parse_tfm.py +211 -0
- claude_mpm/experimental/cli_enhancements.py +2 -1
- claude_mpm/hooks/claude_hooks/INTEGRATION_EXAMPLE.md +243 -0
- claude_mpm/hooks/claude_hooks/README_AUTO_PAUSE.md +403 -0
- claude_mpm/hooks/claude_hooks/__pycache__/auto_pause_handler.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__/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/auto_pause_handler.py +485 -0
- claude_mpm/hooks/claude_hooks/event_handlers.py +466 -136
- claude_mpm/hooks/claude_hooks/hook_handler.py +204 -104
- claude_mpm/hooks/claude_hooks/hook_wrapper.sh +6 -11
- claude_mpm/hooks/claude_hooks/installer.py +291 -59
- claude_mpm/hooks/claude_hooks/memory_integration.py +52 -32
- claude_mpm/hooks/claude_hooks/response_tracking.py +43 -60
- claude_mpm/hooks/claude_hooks/services/__init__.py +21 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/container.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/protocols.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 +41 -26
- claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +38 -105
- claude_mpm/hooks/claude_hooks/services/container.py +326 -0
- claude_mpm/hooks/claude_hooks/services/protocols.py +328 -0
- claude_mpm/hooks/claude_hooks/services/state_manager.py +25 -38
- claude_mpm/hooks/claude_hooks/services/subagent_processor.py +75 -77
- claude_mpm/hooks/kuzu_memory_hook.py +5 -5
- claude_mpm/hooks/session_resume_hook.py +89 -1
- claude_mpm/hooks/templates/pre_tool_use_simple.py +6 -6
- claude_mpm/hooks/templates/pre_tool_use_template.py +16 -8
- claude_mpm/init.py +224 -4
- claude_mpm/mcp/__init__.py +9 -0
- claude_mpm/mcp/google_workspace_server.py +610 -0
- claude_mpm/scripts/claude-hook-handler.sh +46 -19
- claude_mpm/services/agents/agent_recommendation_service.py +8 -8
- claude_mpm/services/agents/agent_selection_service.py +2 -2
- claude_mpm/services/agents/cache_git_manager.py +1 -1
- claude_mpm/services/agents/deployment/agent_discovery_service.py +3 -1
- claude_mpm/services/agents/deployment/agent_format_converter.py +25 -13
- claude_mpm/services/agents/deployment/agent_template_builder.py +37 -17
- claude_mpm/services/agents/deployment/async_agent_deployment.py +31 -27
- claude_mpm/services/agents/deployment/deployment_reconciler.py +577 -0
- claude_mpm/services/agents/deployment/local_template_deployment.py +3 -1
- claude_mpm/services/agents/deployment/multi_source_deployment_service.py +36 -8
- claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +50 -26
- claude_mpm/services/agents/deployment/startup_reconciliation.py +138 -0
- claude_mpm/services/agents/git_source_manager.py +21 -2
- claude_mpm/services/agents/loading/framework_agent_loader.py +75 -2
- claude_mpm/services/agents/single_tier_deployment_service.py +4 -4
- claude_mpm/services/agents/sources/git_source_sync_service.py +116 -5
- claude_mpm/services/agents/startup_sync.py +5 -2
- claude_mpm/services/cli/__init__.py +3 -0
- claude_mpm/services/cli/incremental_pause_manager.py +561 -0
- claude_mpm/services/cli/session_resume_helper.py +10 -2
- claude_mpm/services/command_deployment_service.py +44 -26
- claude_mpm/services/delegation_detector.py +175 -0
- claude_mpm/services/diagnostics/checks/agent_sources_check.py +30 -0
- claude_mpm/services/diagnostics/checks/configuration_check.py +24 -0
- claude_mpm/services/diagnostics/checks/installation_check.py +22 -0
- claude_mpm/services/diagnostics/checks/mcp_services_check.py +23 -0
- claude_mpm/services/diagnostics/doctor_reporter.py +31 -1
- claude_mpm/services/diagnostics/models.py +14 -1
- claude_mpm/services/event_log.py +325 -0
- claude_mpm/services/hook_installer_service.py +77 -8
- claude_mpm/services/infrastructure/__init__.py +4 -0
- claude_mpm/services/infrastructure/context_usage_tracker.py +291 -0
- claude_mpm/services/infrastructure/resume_log_generator.py +24 -5
- claude_mpm/services/mcp_config_manager.py +99 -19
- claude_mpm/services/mcp_service_registry.py +294 -0
- claude_mpm/services/monitor/daemon_manager.py +15 -4
- claude_mpm/services/monitor/management/lifecycle.py +8 -3
- claude_mpm/services/monitor/server.py +111 -16
- claude_mpm/services/pm_skills_deployer.py +302 -94
- claude_mpm/services/profile_manager.py +10 -4
- claude_mpm/services/skills/git_skill_source_manager.py +192 -29
- claude_mpm/services/skills/selective_skill_deployer.py +211 -46
- claude_mpm/services/skills/skill_discovery_service.py +74 -4
- claude_mpm/services/skills_deployer.py +192 -70
- claude_mpm/services/socketio/handlers/hook.py +14 -7
- claude_mpm/services/socketio/server/main.py +12 -4
- claude_mpm/skills/__init__.py +2 -1
- claude_mpm/skills/bundled/collaboration/brainstorming/SKILL.md +79 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/SKILL.md +178 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/agent-prompts.md +577 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/coordination-patterns.md +467 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/examples.md +537 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/troubleshooting.md +730 -0
- claude_mpm/skills/bundled/collaboration/git-worktrees.md +317 -0
- claude_mpm/skills/bundled/collaboration/requesting-code-review/SKILL.md +112 -0
- claude_mpm/skills/bundled/collaboration/requesting-code-review/references/code-reviewer-template.md +146 -0
- claude_mpm/skills/bundled/collaboration/requesting-code-review/references/review-examples.md +412 -0
- claude_mpm/skills/bundled/collaboration/stacked-prs.md +251 -0
- claude_mpm/skills/bundled/collaboration/writing-plans/SKILL.md +81 -0
- claude_mpm/skills/bundled/collaboration/writing-plans/references/best-practices.md +362 -0
- claude_mpm/skills/bundled/collaboration/writing-plans/references/plan-structure-templates.md +312 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/SKILL.md +152 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/references/advanced-techniques.md +668 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/references/examples.md +587 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/references/integration.md +438 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/references/tracing-techniques.md +391 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/CREATION-LOG.md +119 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/SKILL.md +148 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/references/anti-patterns.md +483 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/references/examples.md +452 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/references/troubleshooting.md +449 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/references/workflow.md +411 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/test-academic.md +14 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-1.md +58 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-2.md +68 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-3.md +69 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/SKILL.md +131 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/references/gate-function.md +325 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/references/integration-and-workflows.md +490 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/references/red-flags-and-failures.md +425 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/references/verification-patterns.md +499 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/INTEGRATION.md +611 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/README.md +596 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/SKILL.md +260 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/examples/nextjs-env-structure.md +315 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/references/frameworks.md +436 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/references/security.md +433 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/references/synchronization.md +452 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/references/troubleshooting.md +404 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/references/validation.md +420 -0
- claude_mpm/skills/bundled/main/artifacts-builder/SKILL.md +86 -0
- claude_mpm/skills/bundled/main/internal-comms/SKILL.md +43 -0
- claude_mpm/skills/bundled/main/internal-comms/examples/3p-updates.md +47 -0
- claude_mpm/skills/bundled/main/internal-comms/examples/company-newsletter.md +65 -0
- claude_mpm/skills/bundled/main/internal-comms/examples/faq-answers.md +30 -0
- claude_mpm/skills/bundled/main/internal-comms/examples/general-comms.md +16 -0
- claude_mpm/skills/bundled/main/mcp-builder/SKILL.md +160 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/design_principles.md +412 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/evaluation.md +602 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/mcp_best_practices.md +915 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/node_mcp_server.md +916 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/python_mcp_server.md +752 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/workflow.md +1237 -0
- claude_mpm/skills/bundled/main/skill-creator/SKILL.md +189 -0
- claude_mpm/skills/bundled/main/skill-creator/references/best-practices.md +500 -0
- claude_mpm/skills/bundled/main/skill-creator/references/creation-workflow.md +464 -0
- claude_mpm/skills/bundled/main/skill-creator/references/examples.md +619 -0
- claude_mpm/skills/bundled/main/skill-creator/references/progressive-disclosure.md +437 -0
- claude_mpm/skills/bundled/main/skill-creator/references/skill-structure.md +231 -0
- claude_mpm/skills/bundled/php/espocrm-development/SKILL.md +170 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/architecture.md +602 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/common-tasks.md +821 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/development-workflow.md +742 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/frontend-customization.md +726 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/hooks-and-services.md +764 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/testing-debugging.md +831 -0
- claude_mpm/skills/bundled/pm/mpm/SKILL.md +38 -0
- claude_mpm/skills/bundled/pm/mpm-agent-update-workflow/SKILL.md +75 -0
- claude_mpm/skills/bundled/pm/mpm-bug-reporting/SKILL.md +248 -0
- claude_mpm/skills/bundled/pm/mpm-circuit-breaker-enforcement/SKILL.md +476 -0
- claude_mpm/skills/bundled/pm/mpm-config/SKILL.md +29 -0
- claude_mpm/skills/bundled/pm/mpm-delegation-patterns/SKILL.md +167 -0
- claude_mpm/skills/bundled/pm/mpm-doctor/SKILL.md +53 -0
- claude_mpm/skills/bundled/pm/mpm-git-file-tracking/SKILL.md +113 -0
- claude_mpm/skills/bundled/pm/mpm-help/SKILL.md +35 -0
- claude_mpm/skills/bundled/pm/mpm-init/SKILL.md +125 -0
- claude_mpm/skills/bundled/pm/mpm-monitor/SKILL.md +32 -0
- claude_mpm/skills/bundled/pm/mpm-organize/SKILL.md +121 -0
- claude_mpm/skills/bundled/pm/mpm-postmortem/SKILL.md +22 -0
- claude_mpm/skills/bundled/pm/mpm-pr-workflow/SKILL.md +124 -0
- claude_mpm/skills/bundled/pm/mpm-session-management/SKILL.md +312 -0
- claude_mpm/skills/bundled/pm/mpm-session-pause/SKILL.md +170 -0
- claude_mpm/skills/bundled/pm/mpm-session-resume/SKILL.md +31 -0
- claude_mpm/skills/bundled/pm/mpm-status/SKILL.md +37 -0
- claude_mpm/skills/bundled/pm/mpm-teaching-mode/SKILL.md +657 -0
- claude_mpm/skills/bundled/pm/mpm-ticket-view/SKILL.md +110 -0
- claude_mpm/skills/bundled/pm/mpm-ticketing-integration/SKILL.md +154 -0
- claude_mpm/skills/bundled/pm/mpm-tool-usage-guide/SKILL.md +386 -0
- claude_mpm/skills/bundled/pm/mpm-verification-protocols/SKILL.md +198 -0
- claude_mpm/skills/bundled/pm/mpm-version/SKILL.md +21 -0
- claude_mpm/skills/bundled/react/flexlayout-react.md +742 -0
- claude_mpm/skills/bundled/rust/desktop-applications/SKILL.md +226 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/architecture-patterns.md +901 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/native-gui-frameworks.md +901 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/platform-integration.md +775 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/state-management.md +937 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/tauri-framework.md +770 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/testing-deployment.md +961 -0
- claude_mpm/skills/bundled/security-scanning.md +112 -0
- claude_mpm/skills/bundled/tauri/tauri-async-patterns.md +495 -0
- claude_mpm/skills/bundled/tauri/tauri-build-deploy.md +599 -0
- claude_mpm/skills/bundled/tauri/tauri-command-patterns.md +535 -0
- claude_mpm/skills/bundled/tauri/tauri-error-handling.md +613 -0
- claude_mpm/skills/bundled/tauri/tauri-event-system.md +648 -0
- claude_mpm/skills/bundled/tauri/tauri-file-system.md +673 -0
- claude_mpm/skills/bundled/tauri/tauri-frontend-integration.md +767 -0
- claude_mpm/skills/bundled/tauri/tauri-performance.md +669 -0
- claude_mpm/skills/bundled/tauri/tauri-state-management.md +573 -0
- claude_mpm/skills/bundled/tauri/tauri-testing.md +384 -0
- claude_mpm/skills/bundled/tauri/tauri-window-management.md +628 -0
- claude_mpm/skills/bundled/testing/condition-based-waiting/SKILL.md +119 -0
- claude_mpm/skills/bundled/testing/condition-based-waiting/references/patterns-and-implementation.md +253 -0
- claude_mpm/skills/bundled/testing/test-driven-development/SKILL.md +145 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/anti-patterns.md +543 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/examples.md +741 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/integration.md +470 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/philosophy.md +458 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/workflow.md +639 -0
- claude_mpm/skills/bundled/testing/test-quality-inspector/SKILL.md +458 -0
- claude_mpm/skills/bundled/testing/test-quality-inspector/examples/example-inspection-report.md +411 -0
- claude_mpm/skills/bundled/testing/test-quality-inspector/references/assertion-quality.md +317 -0
- claude_mpm/skills/bundled/testing/test-quality-inspector/references/inspection-checklist.md +270 -0
- claude_mpm/skills/bundled/testing/test-quality-inspector/references/red-flags.md +436 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/SKILL.md +140 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/references/completeness-anti-patterns.md +572 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/references/core-anti-patterns.md +411 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/references/detection-guide.md +569 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/references/tdd-connection.md +695 -0
- claude_mpm/skills/bundled/testing/webapp-testing/SKILL.md +184 -0
- claude_mpm/skills/bundled/testing/webapp-testing/decision-tree.md +459 -0
- claude_mpm/skills/bundled/testing/webapp-testing/playwright-patterns.md +479 -0
- claude_mpm/skills/bundled/testing/webapp-testing/reconnaissance-pattern.md +687 -0
- claude_mpm/skills/bundled/testing/webapp-testing/server-management.md +758 -0
- claude_mpm/skills/bundled/testing/webapp-testing/troubleshooting.md +868 -0
- claude_mpm/skills/registry.py +295 -90
- claude_mpm/skills/skill_manager.py +29 -23
- claude_mpm/templates/.pre-commit-config.yaml +112 -0
- claude_mpm/utils/agent_dependency_loader.py +103 -4
- claude_mpm/utils/robust_installer.py +45 -24
- claude_mpm-5.6.72.dist-info/METADATA +416 -0
- {claude_mpm-5.4.41.dist-info → claude_mpm-5.6.72.dist-info}/RECORD +477 -159
- {claude_mpm-5.4.41.dist-info → claude_mpm-5.6.72.dist-info}/WHEEL +1 -1
- {claude_mpm-5.4.41.dist-info → claude_mpm-5.6.72.dist-info}/entry_points.txt +2 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.B_FtCwCQ.css +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.Cl_eSA4x.css +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BgChzWQ1.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CIXEwuWe.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CWc5urbQ.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DMkZpdF2.js +0 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/app.DTL5mJO-.js +0 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.DzuEhzqh.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/1.DFLC8jdE.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.DPvEihJJ.js +0 -10
- claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager.cpython-311.pyc +0 -0
- claude_mpm-5.4.41.dist-info/METADATA +0 -998
- {claude_mpm-5.4.41.dist-info → claude_mpm-5.6.72.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-5.4.41.dist-info → claude_mpm-5.6.72.dist-info}/licenses/LICENSE-FAQ.md +0 -0
- {claude_mpm-5.4.41.dist-info → claude_mpm-5.6.72.dist-info}/top_level.txt +0 -0
claude_mpm/cli/startup.py
CHANGED
|
@@ -13,6 +13,49 @@ import sys
|
|
|
13
13
|
from pathlib import Path
|
|
14
14
|
|
|
15
15
|
|
|
16
|
+
def cleanup_user_level_hooks() -> bool:
|
|
17
|
+
"""Remove stale user-level hooks directory.
|
|
18
|
+
|
|
19
|
+
WHY: claude-mpm previously deployed hooks to ~/.claude/hooks/claude-mpm/
|
|
20
|
+
(user-level). This is now deprecated in favor of project-level hooks
|
|
21
|
+
configured in .claude/settings.local.json. Stale user-level hooks can
|
|
22
|
+
cause conflicts and confusion.
|
|
23
|
+
|
|
24
|
+
DESIGN DECISION: Runs early in startup, before project hook sync.
|
|
25
|
+
Non-blocking - failures are logged at debug level but don't prevent startup.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
bool: True if hooks were cleaned up, False if none found or cleanup failed
|
|
29
|
+
"""
|
|
30
|
+
import shutil
|
|
31
|
+
|
|
32
|
+
user_hooks_dir = Path.home() / ".claude" / "hooks" / "claude-mpm"
|
|
33
|
+
|
|
34
|
+
if not user_hooks_dir.exists():
|
|
35
|
+
return False
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
from ..core.logger import get_logger
|
|
39
|
+
|
|
40
|
+
logger = get_logger("startup")
|
|
41
|
+
logger.debug(f"Removing stale user-level hooks directory: {user_hooks_dir}")
|
|
42
|
+
|
|
43
|
+
shutil.rmtree(user_hooks_dir)
|
|
44
|
+
|
|
45
|
+
logger.debug("User-level hooks cleanup complete")
|
|
46
|
+
return True
|
|
47
|
+
except Exception as e:
|
|
48
|
+
# Non-critical - log but don't fail startup
|
|
49
|
+
try:
|
|
50
|
+
from ..core.logger import get_logger
|
|
51
|
+
|
|
52
|
+
logger = get_logger("startup")
|
|
53
|
+
logger.debug(f"Failed to cleanup user-level hooks (non-fatal): {e}")
|
|
54
|
+
except Exception: # nosec B110
|
|
55
|
+
pass # Avoid any errors in error handling
|
|
56
|
+
return False
|
|
57
|
+
|
|
58
|
+
|
|
16
59
|
def sync_hooks_on_startup(quiet: bool = False) -> bool:
|
|
17
60
|
"""Ensure hooks are up-to-date on startup.
|
|
18
61
|
|
|
@@ -20,7 +63,12 @@ def sync_hooks_on_startup(quiet: bool = False) -> bool:
|
|
|
20
63
|
Reinstalling hooks ensures the hook format matches the current code.
|
|
21
64
|
|
|
22
65
|
DESIGN DECISION: Shows brief status message on success for user awareness.
|
|
23
|
-
Failures are logged but don't prevent startup to ensure claude-mpm
|
|
66
|
+
Failures are logged but don't prevent startup to ensure claude-mpm
|
|
67
|
+
remains functional.
|
|
68
|
+
|
|
69
|
+
Workflow:
|
|
70
|
+
1. Cleanup stale user-level hooks (~/.claude/hooks/claude-mpm/)
|
|
71
|
+
2. Reinstall project-level hooks to .claude/settings.local.json
|
|
24
72
|
|
|
25
73
|
Args:
|
|
26
74
|
quiet: If True, suppress all output (used internally)
|
|
@@ -28,28 +76,45 @@ def sync_hooks_on_startup(quiet: bool = False) -> bool:
|
|
|
28
76
|
Returns:
|
|
29
77
|
bool: True if hooks were synced successfully, False otherwise
|
|
30
78
|
"""
|
|
79
|
+
is_tty = not quiet and sys.stdout.isatty()
|
|
80
|
+
|
|
81
|
+
# Step 1: Cleanup stale user-level hooks first
|
|
82
|
+
if is_tty:
|
|
83
|
+
print("Cleaning user-level hooks...", end=" ", flush=True)
|
|
84
|
+
|
|
85
|
+
cleaned = cleanup_user_level_hooks()
|
|
86
|
+
|
|
87
|
+
if is_tty:
|
|
88
|
+
if cleaned:
|
|
89
|
+
print("✓")
|
|
90
|
+
else:
|
|
91
|
+
print("(none found)")
|
|
92
|
+
|
|
93
|
+
# Step 2: Install project-level hooks
|
|
31
94
|
try:
|
|
32
95
|
from ..hooks.claude_hooks.installer import HookInstaller
|
|
33
96
|
|
|
34
97
|
installer = HookInstaller()
|
|
35
98
|
|
|
36
99
|
# Show brief status (hooks sync is fast)
|
|
37
|
-
if
|
|
38
|
-
print("
|
|
100
|
+
if is_tty:
|
|
101
|
+
print("Installing project hooks...", end=" ", flush=True)
|
|
39
102
|
|
|
40
103
|
# Reinstall hooks (force=True ensures update)
|
|
41
104
|
success = installer.install_hooks(force=True)
|
|
42
105
|
|
|
43
|
-
if
|
|
106
|
+
if is_tty:
|
|
44
107
|
if success:
|
|
45
|
-
|
|
108
|
+
# Count hooks from settings file
|
|
109
|
+
hook_count = _count_installed_hooks(installer.settings_file)
|
|
110
|
+
print(f"{hook_count} hooks configured ✓")
|
|
46
111
|
else:
|
|
47
112
|
print("(skipped)")
|
|
48
113
|
|
|
49
114
|
return success
|
|
50
115
|
|
|
51
116
|
except Exception as e:
|
|
52
|
-
if
|
|
117
|
+
if is_tty:
|
|
53
118
|
print("(error)")
|
|
54
119
|
# Log but don't fail startup
|
|
55
120
|
from ..core.logger import get_logger
|
|
@@ -59,6 +124,30 @@ def sync_hooks_on_startup(quiet: bool = False) -> bool:
|
|
|
59
124
|
return False
|
|
60
125
|
|
|
61
126
|
|
|
127
|
+
def _count_installed_hooks(settings_file: Path) -> int:
|
|
128
|
+
"""Count the number of hook event types configured in settings.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
settings_file: Path to the settings.local.json file
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
int: Number of hook event types configured (e.g., 7 for all events)
|
|
135
|
+
"""
|
|
136
|
+
import json
|
|
137
|
+
|
|
138
|
+
try:
|
|
139
|
+
if not settings_file.exists():
|
|
140
|
+
return 0
|
|
141
|
+
|
|
142
|
+
with settings_file.open() as f:
|
|
143
|
+
settings = json.load(f)
|
|
144
|
+
|
|
145
|
+
hooks = settings.get("hooks", {})
|
|
146
|
+
return len(hooks)
|
|
147
|
+
except Exception:
|
|
148
|
+
return 0
|
|
149
|
+
|
|
150
|
+
|
|
62
151
|
def cleanup_legacy_agent_cache() -> None:
|
|
63
152
|
"""Remove legacy hierarchical agent cache directories.
|
|
64
153
|
|
|
@@ -161,7 +250,25 @@ def setup_early_environment(argv):
|
|
|
161
250
|
# CRITICAL: Suppress ALL logging by default
|
|
162
251
|
# This catches all loggers (claude_mpm.*, service.*, framework_loader, etc.)
|
|
163
252
|
# This will be overridden by setup_mcp_server_logging() based on user preference
|
|
164
|
-
logging.getLogger()
|
|
253
|
+
root_logger = logging.getLogger()
|
|
254
|
+
root_logger.setLevel(logging.CRITICAL + 1) # Root logger catches everything
|
|
255
|
+
root_logger.handlers = [] # Remove any handlers
|
|
256
|
+
|
|
257
|
+
# Also suppress common module loggers explicitly to prevent handler leakage
|
|
258
|
+
for logger_name in [
|
|
259
|
+
"claude_mpm",
|
|
260
|
+
"path_resolver",
|
|
261
|
+
"file_loader",
|
|
262
|
+
"framework_loader",
|
|
263
|
+
"service",
|
|
264
|
+
"instruction_loader",
|
|
265
|
+
"agent_loader",
|
|
266
|
+
"startup",
|
|
267
|
+
]:
|
|
268
|
+
module_logger = logging.getLogger(logger_name)
|
|
269
|
+
module_logger.setLevel(logging.CRITICAL + 1)
|
|
270
|
+
module_logger.handlers = []
|
|
271
|
+
module_logger.propagate = False
|
|
165
272
|
|
|
166
273
|
# Process argv
|
|
167
274
|
if argv is None:
|
|
@@ -178,7 +285,7 @@ def should_skip_background_services(args, processed_argv):
|
|
|
178
285
|
"""
|
|
179
286
|
Determine if background services should be skipped for this command.
|
|
180
287
|
|
|
181
|
-
WHY: Some commands (help, version, configure, doctor) don't need
|
|
288
|
+
WHY: Some commands (help, version, configure, doctor, oauth) don't need
|
|
182
289
|
background services and should start faster.
|
|
183
290
|
|
|
184
291
|
Args:
|
|
@@ -191,7 +298,18 @@ def should_skip_background_services(args, processed_argv):
|
|
|
191
298
|
skip_commands = ["--version", "-v", "--help", "-h"]
|
|
192
299
|
return any(cmd in (processed_argv or sys.argv[1:]) for cmd in skip_commands) or (
|
|
193
300
|
hasattr(args, "command")
|
|
194
|
-
and args.command
|
|
301
|
+
and args.command
|
|
302
|
+
in [
|
|
303
|
+
"info",
|
|
304
|
+
"doctor",
|
|
305
|
+
"config",
|
|
306
|
+
"mcp",
|
|
307
|
+
"configure",
|
|
308
|
+
"hook-errors",
|
|
309
|
+
"autotodos",
|
|
310
|
+
"commander",
|
|
311
|
+
"oauth",
|
|
312
|
+
]
|
|
195
313
|
)
|
|
196
314
|
|
|
197
315
|
|
|
@@ -234,7 +352,7 @@ def deploy_bundled_skills():
|
|
|
234
352
|
if not skills_config.get("auto_deploy", True):
|
|
235
353
|
# Auto-deploy disabled, skip silently
|
|
236
354
|
return
|
|
237
|
-
except Exception:
|
|
355
|
+
except Exception: # nosec B110
|
|
238
356
|
# If config loading fails, assume auto-deploy is enabled (default)
|
|
239
357
|
pass
|
|
240
358
|
|
|
@@ -252,11 +370,13 @@ def deploy_bundled_skills():
|
|
|
252
370
|
if deployment_result.get("deployed"):
|
|
253
371
|
# Show simple feedback for deployed skills
|
|
254
372
|
deployed_count = len(deployment_result["deployed"])
|
|
255
|
-
|
|
373
|
+
if sys.stdout.isatty():
|
|
374
|
+
print(f"✓ Bundled skills ready ({deployed_count} deployed)", flush=True)
|
|
256
375
|
logger.info(f"Skills: Deployed {deployed_count} skill(s)")
|
|
257
376
|
elif not deployment_result.get("errors"):
|
|
258
377
|
# No deployment needed, skills already present
|
|
259
|
-
|
|
378
|
+
if sys.stdout.isatty():
|
|
379
|
+
print("✓ Bundled skills ready", flush=True)
|
|
260
380
|
|
|
261
381
|
if deployment_result.get("errors"):
|
|
262
382
|
logger.warning(
|
|
@@ -290,7 +410,8 @@ def discover_and_link_runtime_skills():
|
|
|
290
410
|
|
|
291
411
|
discover_skills()
|
|
292
412
|
# Show simple success feedback
|
|
293
|
-
|
|
413
|
+
if sys.stdout.isatty():
|
|
414
|
+
print("✓ Runtime skills linked", flush=True)
|
|
294
415
|
except Exception as e:
|
|
295
416
|
# Import logger here to avoid circular imports
|
|
296
417
|
from ..core.logger import get_logger
|
|
@@ -308,63 +429,62 @@ def deploy_output_style_on_startup():
|
|
|
308
429
|
communication without emojis and exclamation points. Styles are project-specific
|
|
309
430
|
to allow different projects to have different communication styles.
|
|
310
431
|
|
|
311
|
-
DESIGN DECISION: This is non-blocking and idempotent. Deploys to
|
|
312
|
-
directory (
|
|
313
|
-
|
|
432
|
+
DESIGN DECISION: This is non-blocking and idempotent. Deploys to user-level
|
|
433
|
+
directory (~/.claude/output-styles/) which is the official Claude Code location
|
|
434
|
+
for custom output styles.
|
|
314
435
|
|
|
315
|
-
Deploys
|
|
316
|
-
- claude-mpm
|
|
436
|
+
Deploys all styles:
|
|
437
|
+
- claude-mpm.md (professional mode)
|
|
317
438
|
- claude-mpm-teacher.md (teaching mode)
|
|
439
|
+
- claude-mpm-research.md (research mode - for codebase analysis)
|
|
318
440
|
"""
|
|
319
441
|
try:
|
|
320
|
-
import
|
|
321
|
-
from pathlib import Path
|
|
442
|
+
from ..core.output_style_manager import OutputStyleManager
|
|
322
443
|
|
|
323
|
-
#
|
|
324
|
-
|
|
325
|
-
professional_source = package_dir / "CLAUDE_MPM_OUTPUT_STYLE.md"
|
|
326
|
-
teacher_source = package_dir / "CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md"
|
|
444
|
+
# Initialize the output style manager
|
|
445
|
+
manager = OutputStyleManager()
|
|
327
446
|
|
|
328
|
-
#
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
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
|
-
)
|
|
447
|
+
# Check if Claude Code version supports output styles (>= 1.0.83)
|
|
448
|
+
if not manager.supports_output_styles():
|
|
449
|
+
# Skip deployment for older versions
|
|
450
|
+
# The manager will fall back to injecting content directly
|
|
451
|
+
return
|
|
344
452
|
|
|
345
|
-
if
|
|
453
|
+
# Check if all styles are already deployed and up-to-date
|
|
454
|
+
all_up_to_date = True
|
|
455
|
+
for style_config in manager.styles.values():
|
|
456
|
+
source_path = style_config["source"]
|
|
457
|
+
target_path = style_config["target"]
|
|
458
|
+
|
|
459
|
+
if not (
|
|
460
|
+
target_path.exists()
|
|
461
|
+
and source_path.exists()
|
|
462
|
+
and target_path.stat().st_size == source_path.stat().st_size
|
|
463
|
+
):
|
|
464
|
+
all_up_to_date = False
|
|
465
|
+
break
|
|
466
|
+
|
|
467
|
+
if all_up_to_date:
|
|
346
468
|
# Show feedback that output styles are ready
|
|
347
|
-
|
|
469
|
+
if sys.stdout.isatty():
|
|
470
|
+
print("✓ Output styles ready", flush=True)
|
|
348
471
|
return
|
|
349
472
|
|
|
350
|
-
# Deploy
|
|
351
|
-
|
|
352
|
-
if professional_source.exists():
|
|
353
|
-
shutil.copy2(professional_source, professional_target)
|
|
354
|
-
deployed_count += 1
|
|
473
|
+
# Deploy all styles using the manager
|
|
474
|
+
results = manager.deploy_all_styles(activate_default=True)
|
|
355
475
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
deployed_count += 1
|
|
476
|
+
# Count successful deployments
|
|
477
|
+
deployed_count = sum(1 for success in results.values() if success)
|
|
359
478
|
|
|
360
479
|
if deployed_count > 0:
|
|
361
|
-
|
|
480
|
+
if sys.stdout.isatty():
|
|
481
|
+
print(f"✓ Output styles deployed ({deployed_count} styles)", flush=True)
|
|
362
482
|
else:
|
|
363
|
-
#
|
|
483
|
+
# Deployment failed - log but don't fail startup
|
|
364
484
|
from ..core.logger import get_logger
|
|
365
485
|
|
|
366
486
|
logger = get_logger("cli")
|
|
367
|
-
logger.debug("
|
|
487
|
+
logger.debug("Failed to deploy any output styles")
|
|
368
488
|
|
|
369
489
|
except Exception as e:
|
|
370
490
|
# Non-critical - log but don't fail startup
|
|
@@ -453,7 +573,95 @@ def _cleanup_orphaned_agents(deploy_target: Path, deployed_agents: list[str]) ->
|
|
|
453
573
|
return removed_count
|
|
454
574
|
|
|
455
575
|
|
|
456
|
-
def
|
|
576
|
+
def _save_deployment_state_after_reconciliation(
|
|
577
|
+
agent_result, project_path: Path
|
|
578
|
+
) -> None:
|
|
579
|
+
"""Save deployment state after reconciliation to prevent duplicate deployment.
|
|
580
|
+
|
|
581
|
+
WHY: After perform_startup_reconciliation() deploys agents to .claude/agents/,
|
|
582
|
+
we need to save a deployment state file so that ClaudeRunner.setup_agents()
|
|
583
|
+
can detect agents are already deployed and skip redundant deployment.
|
|
584
|
+
|
|
585
|
+
This prevents the "✓ Deployed 31 native agents" duplicate deployment that
|
|
586
|
+
occurs when setup_agents() doesn't know reconciliation already ran.
|
|
587
|
+
|
|
588
|
+
Args:
|
|
589
|
+
agent_result: DeploymentResult from perform_startup_reconciliation()
|
|
590
|
+
project_path: Project root directory
|
|
591
|
+
|
|
592
|
+
DESIGN DECISION: Use same state file format as ClaudeRunner._save_deployment_state()
|
|
593
|
+
Located at: .claude-mpm/cache/deployment_state.json
|
|
594
|
+
|
|
595
|
+
State file format:
|
|
596
|
+
{
|
|
597
|
+
"version": "5.6.13",
|
|
598
|
+
"agent_count": 15,
|
|
599
|
+
"deployment_hash": "sha256:...",
|
|
600
|
+
"deployed_at": 1234567890.123
|
|
601
|
+
}
|
|
602
|
+
"""
|
|
603
|
+
import hashlib
|
|
604
|
+
import json
|
|
605
|
+
import time
|
|
606
|
+
|
|
607
|
+
from ..core.logger import get_logger
|
|
608
|
+
|
|
609
|
+
logger = get_logger("cli")
|
|
610
|
+
|
|
611
|
+
try:
|
|
612
|
+
# Get version from package
|
|
613
|
+
from claude_mpm import __version__
|
|
614
|
+
|
|
615
|
+
# Path to state file (matches ClaudeRunner._get_deployment_state_path())
|
|
616
|
+
state_file = project_path / ".claude-mpm" / "cache" / "deployment_state.json"
|
|
617
|
+
agents_dir = project_path / ".claude" / "agents"
|
|
618
|
+
|
|
619
|
+
# Count deployed agents
|
|
620
|
+
if agents_dir.exists():
|
|
621
|
+
agent_count = len(list(agents_dir.glob("*.md")))
|
|
622
|
+
else:
|
|
623
|
+
agent_count = 0
|
|
624
|
+
|
|
625
|
+
# Calculate deployment hash (matches ClaudeRunner._calculate_deployment_hash())
|
|
626
|
+
# CRITICAL: Must match exact hash algorithm used in ClaudeRunner
|
|
627
|
+
# Hashes filename + file content (not mtime) for consistency
|
|
628
|
+
deployment_hash = ""
|
|
629
|
+
if agents_dir.exists():
|
|
630
|
+
agent_files = sorted(agents_dir.glob("*.md"))
|
|
631
|
+
hash_obj = hashlib.sha256()
|
|
632
|
+
for agent_file in agent_files:
|
|
633
|
+
# Include filename and content in hash (matches ClaudeRunner)
|
|
634
|
+
hash_obj.update(agent_file.name.encode())
|
|
635
|
+
try:
|
|
636
|
+
hash_obj.update(agent_file.read_bytes())
|
|
637
|
+
except Exception as e:
|
|
638
|
+
logger.debug(f"Error reading {agent_file} for hash: {e}")
|
|
639
|
+
|
|
640
|
+
deployment_hash = hash_obj.hexdigest()
|
|
641
|
+
|
|
642
|
+
# Create state data
|
|
643
|
+
state_data = {
|
|
644
|
+
"version": __version__,
|
|
645
|
+
"agent_count": agent_count,
|
|
646
|
+
"deployment_hash": deployment_hash,
|
|
647
|
+
"deployed_at": time.time(),
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
# Ensure directory exists
|
|
651
|
+
state_file.parent.mkdir(parents=True, exist_ok=True)
|
|
652
|
+
|
|
653
|
+
# Write state file
|
|
654
|
+
state_file.write_text(json.dumps(state_data, indent=2))
|
|
655
|
+
logger.debug(
|
|
656
|
+
f"Saved deployment state after reconciliation: {agent_count} agents"
|
|
657
|
+
)
|
|
658
|
+
|
|
659
|
+
except Exception as e:
|
|
660
|
+
# Non-critical error - log but don't fail startup
|
|
661
|
+
logger.debug(f"Failed to save deployment state: {e}")
|
|
662
|
+
|
|
663
|
+
|
|
664
|
+
def sync_remote_agents_on_startup(force_sync: bool = False):
|
|
457
665
|
"""
|
|
458
666
|
Synchronize agent templates from remote sources on startup.
|
|
459
667
|
|
|
@@ -466,28 +674,28 @@ def sync_remote_agents_on_startup():
|
|
|
466
674
|
block startup to ensure claude-mpm remains functional.
|
|
467
675
|
|
|
468
676
|
Workflow:
|
|
469
|
-
1.
|
|
470
|
-
2.
|
|
471
|
-
3.
|
|
472
|
-
4. Cleanup
|
|
677
|
+
1. Sync all enabled Git sources (download/cache files) - Phase 1 progress bar
|
|
678
|
+
2. Deploy agents to ~/.claude/agents/ - Phase 2 progress bar
|
|
679
|
+
3. Cleanup orphaned agents (ours but no longer deployed) - Phase 3
|
|
680
|
+
4. Cleanup legacy agent cache directories (after sync/deployment) - Phase 4
|
|
473
681
|
5. Log deployment results
|
|
474
|
-
"""
|
|
475
|
-
# Cleanup legacy cache directories first (before syncing)
|
|
476
|
-
cleanup_legacy_agent_cache()
|
|
477
682
|
|
|
478
|
-
|
|
683
|
+
Args:
|
|
684
|
+
force_sync: Force download even if cache is fresh (bypasses ETag).
|
|
685
|
+
"""
|
|
686
|
+
# DEPRECATED: Legacy warning - no-op function, kept for compatibility
|
|
479
687
|
check_legacy_cache()
|
|
480
688
|
|
|
481
689
|
try:
|
|
690
|
+
# Load active profile if configured
|
|
691
|
+
# Get project root (where .claude-mpm exists)
|
|
692
|
+
from pathlib import Path
|
|
693
|
+
|
|
482
694
|
from ..core.shared.config_loader import ConfigLoader
|
|
483
|
-
from ..services.agents.deployment.agent_deployment import AgentDeploymentService
|
|
484
695
|
from ..services.agents.startup_sync import sync_agents_on_startup
|
|
485
696
|
from ..services.profile_manager import ProfileManager
|
|
486
697
|
from ..utils.progress import ProgressBar
|
|
487
698
|
|
|
488
|
-
# Load active profile if configured
|
|
489
|
-
# Get project root (where .claude-mpm exists)
|
|
490
|
-
from pathlib import Path
|
|
491
699
|
project_root = Path.cwd()
|
|
492
700
|
|
|
493
701
|
profile_manager = ProfileManager(project_dir=project_root)
|
|
@@ -508,7 +716,7 @@ def sync_remote_agents_on_startup():
|
|
|
508
716
|
)
|
|
509
717
|
|
|
510
718
|
# Phase 1: Sync files from Git sources
|
|
511
|
-
result = sync_agents_on_startup()
|
|
719
|
+
result = sync_agents_on_startup(force_refresh=force_sync)
|
|
512
720
|
|
|
513
721
|
# Only proceed with deployment if sync was enabled and ran
|
|
514
722
|
if result.get("enabled") and result.get("sources_synced", 0) > 0:
|
|
@@ -531,297 +739,95 @@ def sync_remote_agents_on_startup():
|
|
|
531
739
|
logger.warning(f"Agent sync completed with {len(errors)} errors")
|
|
532
740
|
|
|
533
741
|
# Phase 2: Deploy agents from cache to ~/.claude/agents/
|
|
534
|
-
#
|
|
742
|
+
# Use reconciliation service to respect configuration.yaml settings
|
|
535
743
|
try:
|
|
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)
|
|
577
|
-
|
|
578
|
-
# Count agents in cache to show accurate progress
|
|
579
744
|
from pathlib import Path
|
|
580
745
|
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
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
|
-
)
|
|
746
|
+
from ..core.unified_config import UnifiedConfig
|
|
747
|
+
from ..services.agents.deployment.startup_reconciliation import (
|
|
748
|
+
perform_startup_reconciliation,
|
|
749
|
+
)
|
|
644
750
|
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
# (
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
"
|
|
658
|
-
"response_format.md",
|
|
659
|
-
"ticket_completeness_examples.md",
|
|
660
|
-
"validation_templates.md",
|
|
661
|
-
"git_file_tracking.md",
|
|
662
|
-
}
|
|
663
|
-
# Documentation files to exclude (by filename)
|
|
664
|
-
doc_files = {
|
|
665
|
-
"readme.md",
|
|
666
|
-
"changelog.md",
|
|
667
|
-
"contributing.md",
|
|
668
|
-
"implementation-summary.md",
|
|
669
|
-
"reorganization-plan.md",
|
|
670
|
-
"auto-deploy-index.md",
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
# Find all markdown files (after cleanup)
|
|
674
|
-
all_md_files = list(cache_dir.rglob("*.md"))
|
|
675
|
-
|
|
676
|
-
# Filter to only agent files:
|
|
677
|
-
# 1. Must have "/agents/" in path exactly ONCE
|
|
678
|
-
# (current structure)
|
|
679
|
-
# 2. Must not be in PM templates or doc files
|
|
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
|
|
683
|
-
agent_files = [
|
|
684
|
-
f
|
|
685
|
-
for f in all_md_files
|
|
686
|
-
if (
|
|
687
|
-
# Must be in an agent directory (from current
|
|
688
|
-
# cache structure)
|
|
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
|
|
695
|
-
# Exclude PM templates, doc files, and BASE-AGENT
|
|
696
|
-
and f.name.lower() not in pm_templates
|
|
697
|
-
and f.name.lower() not in doc_files
|
|
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
|
-
)
|
|
705
|
-
)
|
|
706
|
-
]
|
|
707
|
-
agent_count = len(agent_files)
|
|
708
|
-
|
|
709
|
-
if agent_count > 0:
|
|
710
|
-
# Deploy agents to project-level directory where Claude Code expects them
|
|
711
|
-
deploy_target = Path.cwd() / ".claude" / "agents"
|
|
712
|
-
deployment_result = deployment_service.deploy_agents(
|
|
713
|
-
target_dir=deploy_target,
|
|
714
|
-
force_rebuild=False, # Only deploy if versions differ
|
|
715
|
-
deployment_mode="update", # Version-aware updates
|
|
716
|
-
config=deploy_config, # Pass config to respect profile filtering
|
|
751
|
+
# Load configuration
|
|
752
|
+
unified_config = UnifiedConfig()
|
|
753
|
+
|
|
754
|
+
# Override with profile settings if active
|
|
755
|
+
if active_profile and profile_manager.active_profile:
|
|
756
|
+
# Get enabled agents from profile (returns Set[str])
|
|
757
|
+
profile_enabled_agents = (
|
|
758
|
+
profile_manager.active_profile.get_enabled_agents()
|
|
759
|
+
)
|
|
760
|
+
# Update config with profile's enabled list (convert Set to List)
|
|
761
|
+
unified_config.agents.enabled = list(profile_enabled_agents)
|
|
762
|
+
logger.info(
|
|
763
|
+
f"Profile '{active_profile}': Using {len(profile_enabled_agents)} enabled agents"
|
|
717
764
|
)
|
|
718
765
|
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
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
|
|
766
|
+
# Perform reconciliation to deploy configured agents
|
|
767
|
+
project_path = Path.cwd()
|
|
768
|
+
agent_result, _skill_result = perform_startup_reconciliation(
|
|
769
|
+
project_path=project_path, config=unified_config, silent=False
|
|
770
|
+
)
|
|
742
771
|
|
|
743
|
-
|
|
772
|
+
# Display results with progress bar
|
|
773
|
+
total_operations = (
|
|
774
|
+
len(agent_result.deployed)
|
|
775
|
+
+ len(agent_result.removed)
|
|
776
|
+
+ len(agent_result.unchanged)
|
|
777
|
+
)
|
|
778
|
+
|
|
779
|
+
if total_operations > 0:
|
|
744
780
|
deploy_progress = ProgressBar(
|
|
745
|
-
total=
|
|
781
|
+
total=total_operations,
|
|
746
782
|
prefix="Deploying agents",
|
|
747
783
|
show_percentage=True,
|
|
748
784
|
show_counter=True,
|
|
749
785
|
)
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
)
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
786
|
+
deploy_progress.update(total_operations)
|
|
787
|
+
|
|
788
|
+
# Build summary message
|
|
789
|
+
deployed = len(agent_result.deployed)
|
|
790
|
+
removed = len(agent_result.removed)
|
|
791
|
+
unchanged = len(agent_result.unchanged)
|
|
792
|
+
|
|
793
|
+
summary_parts = []
|
|
794
|
+
if deployed > 0:
|
|
795
|
+
summary_parts.append(f"{deployed} new")
|
|
796
|
+
if removed > 0:
|
|
797
|
+
summary_parts.append(f"{removed} removed")
|
|
798
|
+
if unchanged > 0:
|
|
799
|
+
summary_parts.append(f"{unchanged} unchanged")
|
|
800
|
+
|
|
801
|
+
summary = f"Complete: {', '.join(summary_parts)}"
|
|
802
|
+
deploy_progress.finish(summary)
|
|
803
|
+
|
|
804
|
+
# Display errors if any
|
|
805
|
+
if agent_result.errors:
|
|
806
|
+
logger.warning(
|
|
807
|
+
f"Agent deployment completed with {len(agent_result.errors)} errors"
|
|
769
808
|
)
|
|
809
|
+
print("\n⚠️ Agent Deployment Errors:")
|
|
810
|
+
max_errors_to_show = 10
|
|
811
|
+
errors_to_display = agent_result.errors[:max_errors_to_show]
|
|
770
812
|
|
|
771
|
-
|
|
772
|
-
|
|
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:
|
|
785
|
-
deploy_progress.finish(
|
|
786
|
-
f"Complete: {total_configured} agents deployed, "
|
|
787
|
-
f"{removed} removed ({agent_count} files in cache)"
|
|
788
|
-
)
|
|
789
|
-
else:
|
|
790
|
-
deploy_progress.finish(
|
|
791
|
-
f"Complete: {total_configured} agents deployed "
|
|
792
|
-
f"({agent_count} files in cache)"
|
|
793
|
-
)
|
|
794
|
-
|
|
795
|
-
# Display deployment errors to user (not just logs)
|
|
796
|
-
deploy_errors = deployment_result.get("errors", [])
|
|
797
|
-
if deploy_errors:
|
|
798
|
-
# Log for debugging
|
|
799
|
-
logger.warning(
|
|
800
|
-
f"Agent deployment completed with {len(deploy_errors)} errors: {deploy_errors}"
|
|
801
|
-
)
|
|
802
|
-
|
|
803
|
-
# Display errors to user with clear formatting
|
|
804
|
-
print("\n⚠️ Agent Deployment Errors:")
|
|
805
|
-
|
|
806
|
-
# Show first 10 errors to avoid overwhelming output
|
|
807
|
-
max_errors_to_show = 10
|
|
808
|
-
errors_to_display = deploy_errors[:max_errors_to_show]
|
|
813
|
+
for error in errors_to_display:
|
|
814
|
+
print(f" - {error}")
|
|
809
815
|
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
print(f" - {error}")
|
|
816
|
+
if len(agent_result.errors) > max_errors_to_show:
|
|
817
|
+
remaining = len(agent_result.errors) - max_errors_to_show
|
|
818
|
+
print(f" ... and {remaining} more error(s)")
|
|
814
819
|
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
820
|
+
print(
|
|
821
|
+
f"\n❌ Failed to deploy {len(agent_result.errors)} agent(s). "
|
|
822
|
+
"Please check the error messages above."
|
|
823
|
+
)
|
|
824
|
+
print(" Run with --verbose for detailed error information.\n")
|
|
819
825
|
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
826
|
+
# Save deployment state to prevent duplicate deployment in ClaudeRunner
|
|
827
|
+
# This ensures setup_agents() skips deployment since we already reconciled
|
|
828
|
+
_save_deployment_state_after_reconciliation(
|
|
829
|
+
agent_result=agent_result, project_path=project_path
|
|
830
|
+
)
|
|
825
831
|
|
|
826
832
|
except Exception as e:
|
|
827
833
|
# Deployment failure shouldn't block startup
|
|
@@ -830,6 +836,11 @@ def sync_remote_agents_on_startup():
|
|
|
830
836
|
logger = get_logger("cli")
|
|
831
837
|
logger.warning(f"Failed to deploy agents from cache: {e}")
|
|
832
838
|
|
|
839
|
+
# Phase 4: Cleanup legacy agent cache directories (after sync/deployment)
|
|
840
|
+
# CRITICAL: This must run AFTER sync completes because sync may recreate
|
|
841
|
+
# legacy directories. Running cleanup here ensures they're removed.
|
|
842
|
+
cleanup_legacy_agent_cache()
|
|
843
|
+
|
|
833
844
|
except Exception as e:
|
|
834
845
|
# Non-critical - log but don't fail startup
|
|
835
846
|
from ..core.logger import get_logger
|
|
@@ -838,8 +849,14 @@ def sync_remote_agents_on_startup():
|
|
|
838
849
|
logger.debug(f"Failed to sync remote agents: {e}")
|
|
839
850
|
# Continue execution - agent sync failure shouldn't block startup
|
|
840
851
|
|
|
852
|
+
# Cleanup legacy cache even if sync failed
|
|
853
|
+
try:
|
|
854
|
+
cleanup_legacy_agent_cache()
|
|
855
|
+
except Exception: # nosec B110
|
|
856
|
+
pass # Ignore cleanup errors
|
|
857
|
+
|
|
841
858
|
|
|
842
|
-
def sync_remote_skills_on_startup():
|
|
859
|
+
def sync_remote_skills_on_startup(force_sync: bool = False):
|
|
843
860
|
"""
|
|
844
861
|
Synchronize skill templates from remote sources on startup.
|
|
845
862
|
|
|
@@ -857,6 +874,9 @@ def sync_remote_skills_on_startup():
|
|
|
857
874
|
4. Apply profile filtering if active
|
|
858
875
|
5. Deploy resolved skills to ~/.claude/skills/ - Phase 2 progress bar
|
|
859
876
|
6. Log deployment results with source indication
|
|
877
|
+
|
|
878
|
+
Args:
|
|
879
|
+
force_sync: Force download even if cache is fresh (bypasses ETag).
|
|
860
880
|
"""
|
|
861
881
|
try:
|
|
862
882
|
from pathlib import Path
|
|
@@ -962,7 +982,7 @@ def sync_remote_skills_on_startup():
|
|
|
962
982
|
|
|
963
983
|
# Sync all sources with progress callback
|
|
964
984
|
results = manager.sync_all_sources(
|
|
965
|
-
force=
|
|
985
|
+
force=force_sync, progress_callback=sync_progress.update
|
|
966
986
|
)
|
|
967
987
|
|
|
968
988
|
# Finish sync progress bar with clear breakdown
|
|
@@ -982,150 +1002,191 @@ def sync_remote_skills_on_startup():
|
|
|
982
1002
|
|
|
983
1003
|
# Phase 2: Scan agents and save to configuration.yaml
|
|
984
1004
|
# This step populates configuration.yaml with agent-referenced skills
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
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)
|
|
1005
|
+
# CRITICAL: Always scan agents to populate agent_referenced, even when using cached skills.
|
|
1006
|
+
# Without this, skill_filter=None causes ALL skills to deploy and NO cleanup to run.
|
|
1007
|
+
agents_dir = Path.cwd() / ".claude" / "agents"
|
|
1008
|
+
|
|
1009
|
+
# Scan agents for skill requirements (ALWAYS run to ensure cleanup works)
|
|
1010
|
+
agent_skills = get_required_skills_from_agents(agents_dir)
|
|
1011
|
+
logger.info(
|
|
1012
|
+
f"Agent scan found {len(agent_skills)} unique skills across deployed agents"
|
|
1013
|
+
)
|
|
1010
1014
|
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
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
|
-
)
|
|
1015
|
+
# Save to project-level configuration.yaml
|
|
1016
|
+
project_config_path = Path.cwd() / ".claude-mpm" / "configuration.yaml"
|
|
1017
|
+
save_agent_skills_to_config(list(agent_skills), project_config_path)
|
|
1018
|
+
logger.debug(
|
|
1019
|
+
f"Saved {len(agent_skills)} agent-referenced skills to {project_config_path}"
|
|
1020
|
+
)
|
|
1022
1021
|
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1022
|
+
# Phase 3: Resolve which skills to deploy (user_defined or agent_referenced)
|
|
1023
|
+
skills_to_deploy, skill_source = get_skills_to_deploy(project_config_path)
|
|
1024
|
+
|
|
1025
|
+
# CRITICAL DEBUG: Log deployment resolution to diagnose cleanup issues
|
|
1026
|
+
if skills_to_deploy:
|
|
1027
|
+
logger.info(
|
|
1028
|
+
f"Resolved {len(skills_to_deploy)} skills from {skill_source} (cleanup will run)"
|
|
1029
|
+
)
|
|
1030
|
+
else:
|
|
1031
|
+
logger.warning(
|
|
1032
|
+
f"No skills resolved from {skill_source} - will deploy ALL skills WITHOUT cleanup! "
|
|
1033
|
+
f"This may indicate agent_referenced is empty in configuration.yaml."
|
|
1034
|
+
)
|
|
1035
|
+
|
|
1036
|
+
# Phase 4: Apply profile filtering if active
|
|
1037
|
+
if active_profile and profile_manager.active_profile:
|
|
1038
|
+
# Filter skills based on profile
|
|
1039
|
+
if skills_to_deploy:
|
|
1040
|
+
# Filter the resolved skill list
|
|
1041
|
+
original_count = len(skills_to_deploy)
|
|
1042
|
+
filtered_skills = [
|
|
1043
|
+
skill
|
|
1044
|
+
for skill in skills_to_deploy
|
|
1045
|
+
if profile_manager.is_skill_enabled(skill)
|
|
1046
|
+
]
|
|
1047
|
+
filtered_count = original_count - len(filtered_skills)
|
|
1048
|
+
|
|
1049
|
+
# SAFEGUARD: Warn if all skills were filtered out (misconfiguration)
|
|
1050
|
+
if not filtered_skills and original_count > 0:
|
|
1051
|
+
logger.warning(
|
|
1052
|
+
f"Profile '{active_profile}' filtered ALL {original_count} skills. "
|
|
1053
|
+
f"This may indicate a naming mismatch in the profile."
|
|
1054
|
+
)
|
|
1055
|
+
elif filtered_count > 0:
|
|
1035
1056
|
logger.info(
|
|
1036
|
-
f"Profile '{active_profile}'
|
|
1037
|
-
f"{len(filtered_skills)}
|
|
1057
|
+
f"Profile '{active_profile}' filtered {filtered_count} skills "
|
|
1058
|
+
f"({len(filtered_skills)} remaining)"
|
|
1038
1059
|
)
|
|
1039
1060
|
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1061
|
+
skills_to_deploy = filtered_skills
|
|
1062
|
+
skill_source = f"{skill_source} + profile filtered"
|
|
1063
|
+
else:
|
|
1064
|
+
# No explicit skill list - filter from all available
|
|
1065
|
+
all_skills = manager.get_all_skills()
|
|
1066
|
+
filtered_skills = [
|
|
1067
|
+
skill["name"]
|
|
1068
|
+
for skill in all_skills
|
|
1069
|
+
if profile_manager.is_skill_enabled(skill["name"])
|
|
1070
|
+
]
|
|
1071
|
+
skills_to_deploy = filtered_skills
|
|
1072
|
+
skill_source = "profile filtered"
|
|
1073
|
+
logger.info(
|
|
1074
|
+
f"Profile '{active_profile}': "
|
|
1075
|
+
f"{len(filtered_skills)} skills enabled from {len(all_skills)} available"
|
|
1076
|
+
)
|
|
1043
1077
|
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1078
|
+
# Get all skills to determine counts
|
|
1079
|
+
all_skills = manager.get_all_skills()
|
|
1080
|
+
total_skill_count = len(all_skills)
|
|
1081
|
+
|
|
1082
|
+
# Determine skill count based on resolution
|
|
1083
|
+
skill_count = len(skills_to_deploy) if skills_to_deploy else total_skill_count
|
|
1084
|
+
|
|
1085
|
+
if skill_count > 0:
|
|
1086
|
+
# Deploy skills with resolved filter
|
|
1087
|
+
# Deploy ONLY to project directory (not user-level)
|
|
1088
|
+
# DESIGN DECISION: Project-level deployment keeps skills isolated per project,
|
|
1089
|
+
# avoiding pollution of user's global ~/.claude/skills/ directory.
|
|
1090
|
+
|
|
1091
|
+
# Deploy to project-local directory with cleanup
|
|
1092
|
+
deployment_result = manager.deploy_skills(
|
|
1093
|
+
target_dir=Path.cwd() / ".claude" / "skills",
|
|
1094
|
+
force=force_sync,
|
|
1095
|
+
# CRITICAL FIX: Empty list should mean "deploy no skills", not "deploy all"
|
|
1096
|
+
# When skills_to_deploy is [], we want skill_filter=set() NOT skill_filter=None
|
|
1097
|
+
# None means "no filtering" (deploy all), empty set means "filter to nothing"
|
|
1098
|
+
skill_filter=set(skills_to_deploy)
|
|
1099
|
+
if skills_to_deploy is not None
|
|
1100
|
+
else None,
|
|
1047
1101
|
)
|
|
1048
1102
|
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1103
|
+
# REMOVED: User-level deployment (lines 1068-1074)
|
|
1104
|
+
# Reason: Skills should be project-specific, not user-global.
|
|
1105
|
+
# Claude Code can read from project-level .claude/skills/ directory.
|
|
1106
|
+
|
|
1107
|
+
# Get actual counts from deployment result (use project-local for display)
|
|
1108
|
+
deployed = deployment_result.get("deployed_count", 0)
|
|
1109
|
+
skipped = deployment_result.get("skipped_count", 0)
|
|
1110
|
+
filtered = deployment_result.get("filtered_count", 0)
|
|
1111
|
+
removed = deployment_result.get("removed_count", 0)
|
|
1112
|
+
total_available = deployed + skipped
|
|
1113
|
+
|
|
1114
|
+
# Only show progress bar if there are skills to deploy
|
|
1115
|
+
if total_available > 0:
|
|
1116
|
+
deploy_progress = ProgressBar(
|
|
1117
|
+
total=total_available,
|
|
1118
|
+
prefix="Deploying skill directories",
|
|
1119
|
+
show_percentage=True,
|
|
1120
|
+
show_counter=True,
|
|
1121
|
+
)
|
|
1122
|
+
# Update progress bar to completion
|
|
1123
|
+
deploy_progress.update(total_available)
|
|
1124
|
+
else:
|
|
1125
|
+
# No skills to deploy - create dummy progress for message only
|
|
1126
|
+
deploy_progress = ProgressBar(
|
|
1127
|
+
total=1,
|
|
1128
|
+
prefix="Deploying skill directories",
|
|
1129
|
+
show_percentage=False,
|
|
1130
|
+
show_counter=False,
|
|
1056
1131
|
)
|
|
1132
|
+
deploy_progress.update(1)
|
|
1057
1133
|
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1134
|
+
# Show total available skills (deployed + already existing)
|
|
1135
|
+
# Include source indication (user_defined vs agent_referenced)
|
|
1136
|
+
# Note: total_skill_count is from cache, total_available is what's deployed/needed
|
|
1137
|
+
source_label = (
|
|
1138
|
+
"user override" if skill_source == "user_defined" else "from agents"
|
|
1139
|
+
)
|
|
1063
1140
|
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
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)
|
|
1141
|
+
# Build finish message with cleanup info
|
|
1142
|
+
if deployed > 0 or removed > 0:
|
|
1143
|
+
parts = []
|
|
1144
|
+
if deployed > 0:
|
|
1145
|
+
parts.append(f"{deployed} new")
|
|
1146
|
+
if skipped > 0:
|
|
1147
|
+
parts.append(f"{skipped} unchanged")
|
|
1148
|
+
if removed > 0:
|
|
1149
|
+
parts.append(f"{removed} removed")
|
|
1083
1150
|
|
|
1084
|
-
|
|
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
|
-
)
|
|
1151
|
+
status = ", ".join(parts)
|
|
1090
1152
|
|
|
1091
|
-
if
|
|
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
|
|
1153
|
+
if filtered > 0:
|
|
1104
1154
|
deploy_progress.finish(
|
|
1105
|
-
f"
|
|
1155
|
+
f"Complete: {status} ({total_available} {source_label}, {filtered} files in cache)"
|
|
1106
1156
|
)
|
|
1107
1157
|
else:
|
|
1108
1158
|
deploy_progress.finish(
|
|
1109
|
-
f"Complete: {total_available} skills {source_label} "
|
|
1110
|
-
f"({total_skill_count} files in cache)"
|
|
1159
|
+
f"Complete: {status} ({total_available} skills {source_label} from {total_skill_count} files in cache)"
|
|
1111
1160
|
)
|
|
1161
|
+
elif filtered > 0:
|
|
1162
|
+
# Skills filtered means agents require fewer skills than available
|
|
1163
|
+
deploy_progress.finish(
|
|
1164
|
+
f"No skills needed ({source_label}, {total_skill_count} files in cache)"
|
|
1165
|
+
)
|
|
1166
|
+
else:
|
|
1167
|
+
# No changes - all skills already deployed
|
|
1168
|
+
msg = f"Complete: {total_available} skills {source_label}"
|
|
1169
|
+
if removed > 0:
|
|
1170
|
+
msg += f", {removed} removed"
|
|
1171
|
+
msg += f" ({total_skill_count} files in cache)"
|
|
1172
|
+
deploy_progress.finish(msg)
|
|
1173
|
+
|
|
1174
|
+
# Log deployment errors if any
|
|
1175
|
+
from ..core.logger import get_logger
|
|
1112
1176
|
|
|
1113
|
-
|
|
1114
|
-
from ..core.logger import get_logger
|
|
1115
|
-
|
|
1116
|
-
logger = get_logger("cli")
|
|
1177
|
+
logger = get_logger("cli")
|
|
1117
1178
|
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1179
|
+
errors = deployment_result.get("errors", [])
|
|
1180
|
+
if errors:
|
|
1181
|
+
logger.warning(
|
|
1182
|
+
f"Skill deployment completed with {len(errors)} errors: {errors}"
|
|
1183
|
+
)
|
|
1123
1184
|
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1185
|
+
# Log sync errors if any
|
|
1186
|
+
if results["failed_count"] > 0:
|
|
1187
|
+
logger.warning(
|
|
1188
|
+
f"Skill sync completed with {results['failed_count']} failures"
|
|
1189
|
+
)
|
|
1129
1190
|
|
|
1130
1191
|
except Exception as e:
|
|
1131
1192
|
# Non-critical - log but don't fail startup
|
|
@@ -1208,7 +1269,7 @@ def show_agent_summary():
|
|
|
1208
1269
|
# Display summary if we have agents
|
|
1209
1270
|
if installed_count > 0 or available_count > 0:
|
|
1210
1271
|
print(
|
|
1211
|
-
f"✓ Agents: {installed_count} deployed / {available_count} cached",
|
|
1272
|
+
f"✓ Agents: {installed_count} deployed / {max(0, available_count - installed_count)} cached",
|
|
1212
1273
|
flush=True,
|
|
1213
1274
|
)
|
|
1214
1275
|
|
|
@@ -1225,61 +1286,64 @@ def show_skill_summary():
|
|
|
1225
1286
|
Display skill availability summary on startup.
|
|
1226
1287
|
|
|
1227
1288
|
WHY: Users should see at a glance how many skills are deployed and available
|
|
1228
|
-
from
|
|
1289
|
+
from cache, similar to the agent summary showing "X deployed / Y cached".
|
|
1290
|
+
|
|
1291
|
+
DESIGN DECISION: Fast, non-blocking check that counts skills from:
|
|
1292
|
+
- Deployed skills: PROJECT-level .claude/skills/ directory
|
|
1293
|
+
- Cached skills: ~/.claude-mpm/cache/skills/ directory (from remote sources)
|
|
1229
1294
|
|
|
1230
|
-
|
|
1231
|
-
directory and collection repos. Shows "X installed (Y available)" format.
|
|
1295
|
+
Shows format: "✓ Skills: X deployed / Y cached"
|
|
1232
1296
|
Failures are silent to avoid blocking startup.
|
|
1233
1297
|
"""
|
|
1234
1298
|
try:
|
|
1235
1299
|
from pathlib import Path
|
|
1236
1300
|
|
|
1237
|
-
# Count deployed skills (
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
if
|
|
1301
|
+
# Count deployed skills (PROJECT-level, not user-level)
|
|
1302
|
+
project_skills_dir = Path.cwd() / ".claude" / "skills"
|
|
1303
|
+
deployed_count = 0
|
|
1304
|
+
if project_skills_dir.exists():
|
|
1241
1305
|
# Count directories with SKILL.md (excludes collection repos)
|
|
1242
1306
|
# Exclude collection directories (obra-superpowers, etc.)
|
|
1243
1307
|
skill_dirs = [
|
|
1244
1308
|
d
|
|
1245
|
-
for d in
|
|
1309
|
+
for d in project_skills_dir.iterdir()
|
|
1246
1310
|
if d.is_dir()
|
|
1247
1311
|
and (d / "SKILL.md").exists()
|
|
1248
1312
|
and not (d / ".git").exists() # Exclude collection repos
|
|
1249
1313
|
]
|
|
1250
|
-
|
|
1314
|
+
deployed_count = len(skill_dirs)
|
|
1251
1315
|
|
|
1252
|
-
# Count
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
):
|
|
1316
|
+
# Count cached skills (from remote sources, not deployed yet)
|
|
1317
|
+
# This matches the agent summary pattern: deployed vs cached
|
|
1318
|
+
cache_dir = Path.home() / ".claude-mpm" / "cache" / "skills"
|
|
1319
|
+
cached_count = 0
|
|
1320
|
+
if cache_dir.exists():
|
|
1321
|
+
# Scan all repository directories in cache
|
|
1322
|
+
# Cache structure: ~/.claude-mpm/cache/skills/{owner}/{repo}/...
|
|
1323
|
+
for repo_dir in cache_dir.rglob("*"):
|
|
1324
|
+
if not repo_dir.is_dir():
|
|
1261
1325
|
continue
|
|
1262
1326
|
|
|
1263
|
-
# Count skill directories
|
|
1327
|
+
# Count skill directories (those with SKILL.md)
|
|
1264
1328
|
# Skills can be nested in: skills/category/skill-name/SKILL.md
|
|
1265
1329
|
# or in flat structure: skill-name/SKILL.md
|
|
1266
|
-
for root, dirs, files in os.walk(
|
|
1330
|
+
for root, dirs, files in os.walk(repo_dir):
|
|
1267
1331
|
if "SKILL.md" in files:
|
|
1268
|
-
# Exclude build artifacts and hidden directories
|
|
1269
|
-
# Get relative path from collection_dir to avoid excluding based on .claude parent
|
|
1332
|
+
# Exclude build artifacts and hidden directories
|
|
1270
1333
|
root_path = Path(root)
|
|
1271
|
-
relative_parts = root_path.relative_to(collection_dir).parts
|
|
1272
1334
|
if not any(
|
|
1273
1335
|
part.startswith(".")
|
|
1274
1336
|
or part in ["dist", "build", "__pycache__"]
|
|
1275
|
-
for part in
|
|
1337
|
+
for part in root_path.parts
|
|
1276
1338
|
):
|
|
1277
|
-
|
|
1339
|
+
cached_count += 1
|
|
1278
1340
|
|
|
1279
|
-
# Display summary
|
|
1280
|
-
|
|
1341
|
+
# Display summary using agent summary format: "X deployed / Y cached"
|
|
1342
|
+
# Only show non-deployed cached skills (subtract deployed from cached)
|
|
1343
|
+
non_deployed_cached = max(0, cached_count - deployed_count)
|
|
1344
|
+
if deployed_count > 0 or non_deployed_cached > 0:
|
|
1281
1345
|
print(
|
|
1282
|
-
f"✓ Skills: {
|
|
1346
|
+
f"✓ Skills: {deployed_count} deployed / {non_deployed_cached} cached",
|
|
1283
1347
|
flush=True,
|
|
1284
1348
|
)
|
|
1285
1349
|
|
|
@@ -1292,33 +1356,74 @@ def show_skill_summary():
|
|
|
1292
1356
|
|
|
1293
1357
|
|
|
1294
1358
|
def verify_and_show_pm_skills():
|
|
1295
|
-
"""Verify PM skills and display status.
|
|
1359
|
+
"""Verify PM skills and display status with enhanced validation.
|
|
1360
|
+
|
|
1361
|
+
WHY: PM skills are CRITICAL for PM agent operation. PM must KNOW if
|
|
1362
|
+
framework knowledge is unavailable at startup. Enhanced validation
|
|
1363
|
+
checks all required skills exist, are not corrupted, and auto-repairs
|
|
1364
|
+
if needed.
|
|
1296
1365
|
|
|
1297
|
-
|
|
1298
|
-
|
|
1366
|
+
Shows deployment status:
|
|
1367
|
+
- "✓ PM skills: 8/8 verified" if all required skills are valid
|
|
1368
|
+
- "⚠ PM skills: 2 missing, auto-repairing..." if issues detected
|
|
1369
|
+
- Non-blocking but visible warning if auto-repair fails
|
|
1299
1370
|
"""
|
|
1300
1371
|
try:
|
|
1301
1372
|
from pathlib import Path
|
|
1302
1373
|
|
|
1303
|
-
from ..services.pm_skills_deployer import
|
|
1374
|
+
from ..services.pm_skills_deployer import (
|
|
1375
|
+
REQUIRED_PM_SKILLS,
|
|
1376
|
+
PMSkillsDeployerService,
|
|
1377
|
+
)
|
|
1304
1378
|
|
|
1305
1379
|
deployer = PMSkillsDeployerService()
|
|
1306
1380
|
project_dir = Path.cwd()
|
|
1307
1381
|
|
|
1308
|
-
|
|
1382
|
+
# Verify with auto-repair enabled
|
|
1383
|
+
result = deployer.verify_pm_skills(project_dir, auto_repair=True)
|
|
1309
1384
|
|
|
1310
1385
|
if result.verified:
|
|
1311
|
-
# Show verified status
|
|
1312
|
-
|
|
1386
|
+
# Show verified status with count
|
|
1387
|
+
total_required = len(REQUIRED_PM_SKILLS)
|
|
1388
|
+
if sys.stdout.isatty():
|
|
1389
|
+
print(
|
|
1390
|
+
f"✓ PM skills: {total_required}/{total_required} verified",
|
|
1391
|
+
flush=True,
|
|
1392
|
+
)
|
|
1313
1393
|
else:
|
|
1314
|
-
#
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1394
|
+
# Show warning with details
|
|
1395
|
+
missing_count = len(result.missing_skills)
|
|
1396
|
+
corrupted_count = len(result.corrupted_skills)
|
|
1397
|
+
|
|
1398
|
+
# Build status message
|
|
1399
|
+
issues = []
|
|
1400
|
+
if missing_count > 0:
|
|
1401
|
+
issues.append(f"{missing_count} missing")
|
|
1402
|
+
if corrupted_count > 0:
|
|
1403
|
+
issues.append(f"{corrupted_count} corrupted")
|
|
1404
|
+
|
|
1405
|
+
status = ", ".join(issues)
|
|
1406
|
+
|
|
1407
|
+
# Check if auto-repair was attempted
|
|
1408
|
+
if "Auto-repaired" in result.message:
|
|
1409
|
+
# Auto-repair succeeded
|
|
1410
|
+
total_required = len(REQUIRED_PM_SKILLS)
|
|
1411
|
+
if sys.stdout.isatty():
|
|
1412
|
+
print(
|
|
1413
|
+
f"✓ PM skills: {total_required}/{total_required} verified (auto-repaired)",
|
|
1414
|
+
flush=True,
|
|
1415
|
+
)
|
|
1320
1416
|
else:
|
|
1321
|
-
|
|
1417
|
+
# Auto-repair failed or not attempted
|
|
1418
|
+
if sys.stdout.isatty():
|
|
1419
|
+
print(f"⚠ PM skills: {status}", flush=True)
|
|
1420
|
+
|
|
1421
|
+
# Log warnings for debugging
|
|
1422
|
+
from ..core.logger import get_logger
|
|
1423
|
+
|
|
1424
|
+
logger = get_logger("cli")
|
|
1425
|
+
for warning in result.warnings:
|
|
1426
|
+
logger.warning(f"PM skills: {warning}")
|
|
1322
1427
|
|
|
1323
1428
|
except ImportError:
|
|
1324
1429
|
# PM skills deployer not available - skip silently
|
|
@@ -1353,7 +1458,7 @@ def auto_install_chrome_devtools_on_startup():
|
|
|
1353
1458
|
if not chrome_devtools_config.get("auto_install", True):
|
|
1354
1459
|
# Auto-install disabled, skip silently
|
|
1355
1460
|
return
|
|
1356
|
-
except Exception:
|
|
1461
|
+
except Exception: # nosec B110
|
|
1357
1462
|
# If config loading fails, assume auto-install is enabled (default)
|
|
1358
1463
|
pass
|
|
1359
1464
|
|
|
@@ -1371,7 +1476,29 @@ def auto_install_chrome_devtools_on_startup():
|
|
|
1371
1476
|
# Continue execution - chrome-devtools installation failure shouldn't block startup
|
|
1372
1477
|
|
|
1373
1478
|
|
|
1374
|
-
def
|
|
1479
|
+
def sync_deployment_on_startup(force_sync: bool = False) -> None:
|
|
1480
|
+
"""Consolidated deployment block: hooks + agents.
|
|
1481
|
+
|
|
1482
|
+
WHY: Groups all deployment tasks into a single logical block for clarity.
|
|
1483
|
+
This ensures hooks and agents are deployed together before other services.
|
|
1484
|
+
|
|
1485
|
+
Order:
|
|
1486
|
+
1. Hook cleanup (remove ~/.claude/hooks/claude-mpm/)
|
|
1487
|
+
2. Hook reinstall (update .claude/settings.local.json)
|
|
1488
|
+
3. Agent sync from remote Git sources
|
|
1489
|
+
|
|
1490
|
+
Args:
|
|
1491
|
+
force_sync: Force download even if cache is fresh (bypasses ETag).
|
|
1492
|
+
"""
|
|
1493
|
+
# Step 1-2: Hooks (cleanup + reinstall handled by sync_hooks_on_startup)
|
|
1494
|
+
sync_hooks_on_startup() # Shows "Syncing Claude Code hooks... ✓"
|
|
1495
|
+
|
|
1496
|
+
# Step 3: Agents from remote sources
|
|
1497
|
+
sync_remote_agents_on_startup(force_sync=force_sync)
|
|
1498
|
+
show_agent_summary() # Display agent counts after deployment
|
|
1499
|
+
|
|
1500
|
+
|
|
1501
|
+
def run_background_services(force_sync: bool = False):
|
|
1375
1502
|
"""
|
|
1376
1503
|
Initialize all background services on startup.
|
|
1377
1504
|
|
|
@@ -1382,18 +1509,19 @@ def run_background_services():
|
|
|
1382
1509
|
explicitly requests them via agent-manager commands. This prevents unwanted
|
|
1383
1510
|
file creation in project .claude/ directories.
|
|
1384
1511
|
See: SystemInstructionsDeployer and agent_deployment.py line 504-509
|
|
1512
|
+
|
|
1513
|
+
Args:
|
|
1514
|
+
force_sync: Force download even if cache is fresh (bypasses ETag).
|
|
1385
1515
|
"""
|
|
1386
|
-
#
|
|
1387
|
-
# RATIONALE: Hooks
|
|
1388
|
-
# This
|
|
1389
|
-
|
|
1516
|
+
# Consolidated deployment block: hooks + agents
|
|
1517
|
+
# RATIONALE: Hooks and agents are deployed together before other services
|
|
1518
|
+
# This ensures the deployment phase is complete before configuration checks
|
|
1519
|
+
sync_deployment_on_startup(force_sync=force_sync)
|
|
1390
1520
|
|
|
1391
1521
|
initialize_project_registry()
|
|
1392
1522
|
check_mcp_auto_configuration()
|
|
1393
1523
|
verify_mcp_gateway_startup()
|
|
1394
1524
|
check_for_updates_async()
|
|
1395
|
-
sync_remote_agents_on_startup() # Sync agents from remote sources
|
|
1396
|
-
show_agent_summary() # Display agent counts after deployment
|
|
1397
1525
|
|
|
1398
1526
|
# Skills deployment order (precedence: remote > bundled)
|
|
1399
1527
|
# 1. Deploy bundled skills first (base layer from package)
|
|
@@ -1401,7 +1529,9 @@ def run_background_services():
|
|
|
1401
1529
|
# 3. Discover and link runtime skills (user-added skills)
|
|
1402
1530
|
# This ensures remote skills take precedence over bundled skills when names conflict
|
|
1403
1531
|
deploy_bundled_skills() # Base layer: package-bundled skills
|
|
1404
|
-
sync_remote_skills_on_startup(
|
|
1532
|
+
sync_remote_skills_on_startup(
|
|
1533
|
+
force_sync=force_sync
|
|
1534
|
+
) # Override layer: Git-based skills (takes precedence)
|
|
1405
1535
|
discover_and_link_runtime_skills() # Discovery: user-added skills
|
|
1406
1536
|
show_skill_summary() # Display skill counts after deployment
|
|
1407
1537
|
verify_and_show_pm_skills() # PM skills verification and status
|
|
@@ -1503,7 +1633,9 @@ def check_mcp_auto_configuration():
|
|
|
1503
1633
|
from ..services.mcp_gateway.auto_configure import check_and_configure_mcp
|
|
1504
1634
|
|
|
1505
1635
|
# Show progress feedback - this operation can take 10+ seconds
|
|
1506
|
-
|
|
1636
|
+
# Only show progress message in TTY mode to avoid interfering with Claude Code's status display
|
|
1637
|
+
if sys.stdout.isatty():
|
|
1638
|
+
print("Checking MCP configuration...", end="", flush=True)
|
|
1507
1639
|
|
|
1508
1640
|
# This function handles all the logic:
|
|
1509
1641
|
# - Checks if already configured
|
|
@@ -1514,11 +1646,17 @@ def check_mcp_auto_configuration():
|
|
|
1514
1646
|
check_and_configure_mcp()
|
|
1515
1647
|
|
|
1516
1648
|
# Clear the "Checking..." message by overwriting with spaces
|
|
1517
|
-
|
|
1649
|
+
# Only use carriage return clearing if stdout is a real TTY
|
|
1650
|
+
if sys.stdout.isatty():
|
|
1651
|
+
print("\r" + " " * 30 + "\r", end="", flush=True)
|
|
1652
|
+
# In non-TTY mode, don't print anything - the "Checking..." message will just remain on its line
|
|
1518
1653
|
|
|
1519
1654
|
except Exception as e:
|
|
1520
1655
|
# Clear progress message on error
|
|
1521
|
-
|
|
1656
|
+
# Only use carriage return clearing if stdout is a real TTY
|
|
1657
|
+
if sys.stdout.isatty():
|
|
1658
|
+
print("\r" + " " * 30 + "\r", end="", flush=True)
|
|
1659
|
+
# In non-TTY mode, don't print anything - the "Checking..." message will just remain on its line
|
|
1522
1660
|
|
|
1523
1661
|
# Non-critical - log but don't fail
|
|
1524
1662
|
from ..core.logger import get_logger
|
|
@@ -1601,7 +1739,7 @@ def verify_mcp_gateway_startup():
|
|
|
1601
1739
|
loop.run_until_complete(
|
|
1602
1740
|
asyncio.gather(*pending, return_exceptions=True)
|
|
1603
1741
|
)
|
|
1604
|
-
except Exception:
|
|
1742
|
+
except Exception: # nosec B110
|
|
1605
1743
|
pass # Ignore cleanup errors
|
|
1606
1744
|
finally:
|
|
1607
1745
|
loop.close()
|
|
@@ -1695,7 +1833,7 @@ def check_for_updates_async():
|
|
|
1695
1833
|
|
|
1696
1834
|
logger = get_logger("upgrade_check")
|
|
1697
1835
|
logger.debug(f"Update check failed (non-critical): {e}")
|
|
1698
|
-
except Exception:
|
|
1836
|
+
except Exception: # nosec B110
|
|
1699
1837
|
pass # Avoid any errors in error handling
|
|
1700
1838
|
finally:
|
|
1701
1839
|
# Properly clean up event loop
|
|
@@ -1710,7 +1848,7 @@ def check_for_updates_async():
|
|
|
1710
1848
|
loop.run_until_complete(
|
|
1711
1849
|
asyncio.gather(*pending, return_exceptions=True)
|
|
1712
1850
|
)
|
|
1713
|
-
except Exception:
|
|
1851
|
+
except Exception: # nosec B110
|
|
1714
1852
|
pass # Ignore cleanup errors
|
|
1715
1853
|
finally:
|
|
1716
1854
|
loop.close()
|