claude-mpm 3.4.10__py3-none-any.whl → 5.4.55__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.
- claude_mpm/BUILD_NUMBER +1 -0
- claude_mpm/VERSION +1 -0
- claude_mpm/__init__.py +50 -12
- claude_mpm/__main__.py +7 -2
- claude_mpm/agents/BASE_AGENT.md +164 -0
- claude_mpm/agents/BASE_ENGINEER.md +658 -0
- claude_mpm/agents/CLAUDE_MPM_OUTPUT_STYLE.md +290 -0
- claude_mpm/agents/CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md +2002 -0
- claude_mpm/agents/MEMORY.md +72 -0
- claude_mpm/agents/PM_INSTRUCTIONS.md +1402 -0
- claude_mpm/agents/WORKFLOW.md +111 -0
- claude_mpm/agents/__init__.py +92 -80
- claude_mpm/agents/agent-template.yaml +83 -0
- claude_mpm/agents/agent_loader.py +560 -745
- claude_mpm/agents/agent_loader_integration.py +53 -55
- claude_mpm/agents/agents_metadata.py +186 -27
- claude_mpm/agents/async_agent_loader.py +436 -0
- claude_mpm/agents/base_agent.json +8 -4
- claude_mpm/agents/frontmatter_validator.py +754 -0
- claude_mpm/agents/system_agent_config.py +222 -155
- claude_mpm/agents/templates/README.md +465 -0
- claude_mpm/agents/templates/__init__.py +17 -13
- claude_mpm/agents/templates/circuit-breakers.md +1391 -0
- claude_mpm/agents/templates/context-management-examples.md +544 -0
- claude_mpm/agents/templates/git-file-tracking.md +584 -0
- claude_mpm/agents/templates/pm-examples.md +474 -0
- claude_mpm/agents/templates/pm-red-flags.md +310 -0
- claude_mpm/agents/templates/pr-workflow-examples.md +427 -0
- claude_mpm/agents/templates/research-gate-examples.md +669 -0
- claude_mpm/agents/templates/response-format.md +583 -0
- claude_mpm/agents/templates/structured-questions-examples.md +615 -0
- claude_mpm/agents/templates/ticket-completeness-examples.md +139 -0
- claude_mpm/agents/templates/ticketing-examples.md +277 -0
- claude_mpm/agents/templates/validation-templates.md +312 -0
- claude_mpm/cli/__init__.py +90 -128
- claude_mpm/cli/__main__.py +33 -0
- claude_mpm/cli/chrome_devtools_installer.py +175 -0
- claude_mpm/cli/commands/__init__.py +36 -12
- claude_mpm/cli/commands/agent_manager.py +1403 -0
- claude_mpm/cli/commands/agent_source.py +774 -0
- claude_mpm/cli/commands/agent_state_manager.py +335 -0
- claude_mpm/cli/commands/agents.py +2503 -168
- claude_mpm/cli/commands/agents_cleanup.py +210 -0
- claude_mpm/cli/commands/agents_discover.py +338 -0
- claude_mpm/cli/commands/aggregate.py +540 -0
- claude_mpm/cli/commands/analyze.py +553 -0
- claude_mpm/cli/commands/analyze_code.py +528 -0
- claude_mpm/cli/commands/auto_configure.py +1053 -0
- claude_mpm/cli/commands/cleanup.py +588 -0
- claude_mpm/cli/commands/cleanup_orphaned_agents.py +150 -0
- claude_mpm/cli/commands/config.py +586 -0
- claude_mpm/cli/commands/configure.py +2654 -0
- claude_mpm/cli/commands/configure_agent_display.py +282 -0
- claude_mpm/cli/commands/configure_behavior_manager.py +204 -0
- claude_mpm/cli/commands/configure_hook_manager.py +225 -0
- claude_mpm/cli/commands/configure_models.py +18 -0
- claude_mpm/cli/commands/configure_navigation.py +184 -0
- claude_mpm/cli/commands/configure_paths.py +104 -0
- claude_mpm/cli/commands/configure_persistence.py +254 -0
- claude_mpm/cli/commands/configure_startup_manager.py +646 -0
- claude_mpm/cli/commands/configure_template_editor.py +497 -0
- claude_mpm/cli/commands/configure_validators.py +73 -0
- claude_mpm/cli/commands/dashboard.py +286 -0
- claude_mpm/cli/commands/debug.py +1386 -0
- claude_mpm/cli/commands/doctor.py +243 -0
- claude_mpm/cli/commands/hook_errors.py +277 -0
- claude_mpm/cli/commands/info.py +195 -74
- claude_mpm/cli/commands/local_deploy.py +534 -0
- claude_mpm/cli/commands/mcp.py +205 -0
- claude_mpm/cli/commands/mcp_command_router.py +161 -0
- claude_mpm/cli/commands/mcp_config.py +154 -0
- claude_mpm/cli/commands/mcp_config_commands.py +20 -0
- claude_mpm/cli/commands/mcp_external_commands.py +249 -0
- claude_mpm/cli/commands/mcp_install_commands.py +346 -0
- claude_mpm/cli/commands/mcp_pipx_config.py +208 -0
- claude_mpm/cli/commands/mcp_server_commands.py +155 -0
- claude_mpm/cli/commands/mcp_setup_external.py +868 -0
- claude_mpm/cli/commands/mcp_tool_commands.py +34 -0
- claude_mpm/cli/commands/memory.py +585 -846
- claude_mpm/cli/commands/monitor.py +228 -310
- claude_mpm/cli/commands/mpm_init/__init__.py +73 -0
- claude_mpm/cli/commands/mpm_init/core.py +759 -0
- claude_mpm/cli/commands/mpm_init/display.py +341 -0
- claude_mpm/cli/commands/mpm_init/git_activity.py +427 -0
- claude_mpm/cli/commands/mpm_init/knowledge_extractor.py +481 -0
- claude_mpm/cli/commands/mpm_init/modes.py +397 -0
- claude_mpm/cli/commands/mpm_init/prompts.py +722 -0
- claude_mpm/cli/commands/mpm_init_cli.py +396 -0
- claude_mpm/cli/commands/mpm_init_handler.py +195 -0
- claude_mpm/cli/commands/postmortem.py +401 -0
- claude_mpm/cli/commands/profile.py +276 -0
- claude_mpm/cli/commands/run.py +910 -488
- claude_mpm/cli/commands/search.py +458 -0
- claude_mpm/cli/commands/skill_source.py +694 -0
- claude_mpm/cli/commands/skills.py +1246 -0
- claude_mpm/cli/commands/summarize.py +413 -0
- claude_mpm/cli/commands/tickets.py +536 -53
- claude_mpm/cli/commands/uninstall.py +176 -0
- claude_mpm/cli/commands/upgrade.py +152 -0
- claude_mpm/cli/commands/verify.py +119 -0
- claude_mpm/cli/executor.py +297 -0
- claude_mpm/cli/helpers.py +105 -0
- claude_mpm/cli/interactive/__init__.py +21 -0
- claude_mpm/cli/interactive/agent_wizard.py +1947 -0
- claude_mpm/cli/interactive/skills_wizard.py +491 -0
- claude_mpm/cli/parser.py +87 -563
- claude_mpm/cli/parsers/__init__.py +35 -0
- claude_mpm/cli/parsers/agent_manager_parser.py +393 -0
- claude_mpm/cli/parsers/agent_source_parser.py +171 -0
- claude_mpm/cli/parsers/agents_parser.py +575 -0
- claude_mpm/cli/parsers/analyze_code_parser.py +170 -0
- claude_mpm/cli/parsers/analyze_parser.py +135 -0
- claude_mpm/cli/parsers/auto_configure_parser.py +120 -0
- claude_mpm/cli/parsers/base_parser.py +644 -0
- claude_mpm/cli/parsers/config_parser.py +208 -0
- claude_mpm/cli/parsers/configure_parser.py +138 -0
- claude_mpm/cli/parsers/dashboard_parser.py +113 -0
- claude_mpm/cli/parsers/debug_parser.py +319 -0
- claude_mpm/cli/parsers/local_deploy_parser.py +227 -0
- claude_mpm/cli/parsers/mcp_parser.py +195 -0
- claude_mpm/cli/parsers/memory_parser.py +138 -0
- claude_mpm/cli/parsers/monitor_parser.py +142 -0
- claude_mpm/cli/parsers/mpm_init_parser.py +311 -0
- claude_mpm/cli/parsers/profile_parser.py +147 -0
- claude_mpm/cli/parsers/run_parser.py +157 -0
- claude_mpm/cli/parsers/search_parser.py +245 -0
- claude_mpm/cli/parsers/skill_source_parser.py +169 -0
- claude_mpm/cli/parsers/skills_parser.py +277 -0
- claude_mpm/cli/parsers/source_parser.py +138 -0
- claude_mpm/cli/parsers/tickets_parser.py +203 -0
- claude_mpm/cli/shared/__init__.py +40 -0
- claude_mpm/cli/shared/argument_patterns.py +205 -0
- claude_mpm/cli/shared/base_command.py +242 -0
- claude_mpm/cli/shared/error_handling.py +242 -0
- claude_mpm/cli/shared/output_formatters.py +241 -0
- claude_mpm/cli/startup.py +1743 -0
- claude_mpm/cli/startup_display.py +480 -0
- claude_mpm/cli/startup_logging.py +839 -0
- claude_mpm/cli/utils.py +136 -47
- claude_mpm/cli_module/__init__.py +6 -6
- claude_mpm/cli_module/args.py +188 -140
- claude_mpm/cli_module/commands.py +79 -70
- claude_mpm/cli_module/migration_example.py +42 -64
- claude_mpm/commands/__init__.py +14 -0
- claude_mpm/commands/mpm-config.md +28 -0
- claude_mpm/commands/mpm-doctor.md +20 -0
- claude_mpm/commands/mpm-help.md +20 -0
- claude_mpm/commands/mpm-init.md +120 -0
- claude_mpm/commands/mpm-monitor.md +31 -0
- claude_mpm/commands/mpm-organize.md +120 -0
- claude_mpm/commands/mpm-postmortem.md +21 -0
- claude_mpm/commands/mpm-session-resume.md +30 -0
- claude_mpm/commands/mpm-status.md +20 -0
- claude_mpm/commands/mpm-ticket-view.md +109 -0
- claude_mpm/commands/mpm-version.md +20 -0
- claude_mpm/commands/mpm.md +31 -0
- claude_mpm/config/__init__.py +42 -2
- claude_mpm/config/agent_config.py +402 -0
- claude_mpm/config/agent_presets.py +488 -0
- claude_mpm/config/agent_sources.py +352 -0
- claude_mpm/config/experimental_features.py +217 -0
- claude_mpm/config/model_config.py +428 -0
- claude_mpm/config/paths.py +258 -0
- claude_mpm/config/skill_presets.py +392 -0
- claude_mpm/config/skill_sources.py +590 -0
- claude_mpm/config/socketio_config.py +125 -83
- claude_mpm/constants.py +132 -22
- claude_mpm/core/__init__.py +62 -36
- claude_mpm/core/agent_name_normalizer.py +71 -73
- claude_mpm/core/agent_registry.py +385 -492
- claude_mpm/core/agent_session_manager.py +81 -70
- claude_mpm/core/api_validator.py +330 -0
- claude_mpm/core/base_service.py +159 -122
- claude_mpm/core/cache.py +560 -0
- claude_mpm/core/claude_runner.py +696 -916
- claude_mpm/core/config.py +613 -122
- claude_mpm/core/config_aliases.py +74 -73
- claude_mpm/core/config_constants.py +314 -0
- claude_mpm/core/constants.py +361 -0
- claude_mpm/core/container.py +646 -104
- claude_mpm/core/enums.py +452 -0
- claude_mpm/core/error_handler.py +623 -0
- claude_mpm/core/exceptions.py +536 -0
- claude_mpm/core/factories.py +105 -109
- claude_mpm/core/file_utils.py +764 -0
- claude_mpm/core/framework/__init__.py +25 -0
- claude_mpm/core/framework/formatters/__init__.py +11 -0
- claude_mpm/core/framework/formatters/capability_generator.py +367 -0
- claude_mpm/core/framework/formatters/content_formatter.py +278 -0
- claude_mpm/core/framework/formatters/context_generator.py +185 -0
- claude_mpm/core/framework/loaders/__init__.py +13 -0
- claude_mpm/core/framework/loaders/agent_loader.py +213 -0
- claude_mpm/core/framework/loaders/file_loader.py +176 -0
- claude_mpm/core/framework/loaders/instruction_loader.py +222 -0
- claude_mpm/core/framework/loaders/packaged_loader.py +232 -0
- claude_mpm/core/framework/processors/__init__.py +11 -0
- claude_mpm/core/framework/processors/memory_processor.py +230 -0
- claude_mpm/core/framework/processors/metadata_processor.py +146 -0
- claude_mpm/core/framework/processors/template_processor.py +244 -0
- claude_mpm/core/framework_loader.py +485 -414
- claude_mpm/core/hook_error_memory.py +381 -0
- claude_mpm/core/hook_manager.py +246 -86
- claude_mpm/core/hook_performance_config.py +147 -0
- claude_mpm/core/injectable_service.py +72 -63
- claude_mpm/core/instruction_reinforcement_hook.py +267 -0
- claude_mpm/core/interactive_session.py +670 -0
- claude_mpm/core/interfaces.py +570 -164
- claude_mpm/core/lazy.py +467 -0
- claude_mpm/core/log_manager.py +707 -0
- claude_mpm/core/logger.py +295 -134
- claude_mpm/core/logging_config.py +474 -0
- claude_mpm/core/logging_utils.py +520 -0
- claude_mpm/core/minimal_framework_loader.py +24 -22
- claude_mpm/core/mixins.py +30 -29
- claude_mpm/core/oneshot_session.py +594 -0
- claude_mpm/core/optimized_agent_loader.py +479 -0
- claude_mpm/core/optimized_startup.py +554 -0
- claude_mpm/core/output_style_manager.py +483 -0
- claude_mpm/core/pm_hook_interceptor.py +197 -82
- claude_mpm/core/protocols/__init__.py +23 -0
- claude_mpm/core/protocols/runner_protocol.py +103 -0
- claude_mpm/core/protocols/session_protocol.py +131 -0
- claude_mpm/core/service_registry.py +153 -116
- claude_mpm/core/session_manager.py +179 -64
- claude_mpm/core/shared/__init__.py +17 -0
- claude_mpm/core/shared/config_loader.py +326 -0
- claude_mpm/core/shared/path_resolver.py +281 -0
- claude_mpm/core/shared/singleton_manager.py +221 -0
- claude_mpm/core/socketio_pool.py +400 -137
- claude_mpm/core/system_context.py +38 -0
- claude_mpm/core/tool_access_control.py +64 -57
- claude_mpm/core/types.py +307 -0
- claude_mpm/core/typing_utils.py +553 -0
- claude_mpm/core/unified_agent_registry.py +969 -0
- claude_mpm/core/unified_config.py +570 -0
- claude_mpm/core/unified_paths.py +941 -0
- claude_mpm/dashboard/__init__.py +12 -0
- claude_mpm/dashboard/api/simple_directory.py +261 -0
- claude_mpm/dashboard/static/svelte-build/_app/env.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.DWzvg0-y.css +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.ThTw9_ym.css +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/4TdZjIqw.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/5shd3_w0.js +24 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/B0uc0UOD.js +36 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/B7RN905-.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/B7xVLGWV.js +2 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BIF9m_hv.js +61 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BKjSRqUr.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BPYeabCQ.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BQaXIfA_.js +331 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BSNlmTZj.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Be7GpZd6.js +7 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Bh0LDWpI.js +145 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BofRWZRR.js +10 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BovzEFCE.js +30 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C30mlcqg.js +165 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C4B-KCzX.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C4JcI4KD.js +122 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CBBdVcY8.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CDuw-vjf.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C_Usid8X.js +15 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Cfqx1Qun.js +10 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CiIAseT4.js +128 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CmKTTxBW.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CnA0NrzZ.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Cs_tUR18.js +24 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Cu_Erd72.js +261 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CyWMqx4W.js +43 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CzZX-COe.js +220 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CzeYkLYB.js +65 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/D3k0OPJN.js +4 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/D9lljYKQ.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DGkLK5U1.js +267 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DI7hHRFL.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DLVjFsZ3.js +139 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DUrLdbGD.js +89 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DVp1hx9R.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DY1XQ8fi.js +2 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DZX00Y4g.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Da0KfYnO.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DaimHw_p.js +68 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Dfy6j1xT.js +323 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Dhb8PKl3.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Dle-35c7.js +64 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DmxopI1J.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DwBR2MJi.js +60 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/GYwsonyD.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Gi6I4Gst.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/NqQ1dWOy.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/RJiighC3.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Vzk33B_K.js +2 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/ZGh7QtNv.js +7 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/bT1r9zLR.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/bTOqqlTd.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/eNVUfhuA.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/iEWssX7S.js +162 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/sQeU3Y1z.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/uuIeMWc-.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/app.D6-I5TpK.js +2 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.NWzMBYRp.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/0.m1gL8KXf.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/1.CgNOuw-d.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.C0GcWctS.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/version.json +1 -0
- claude_mpm/dashboard/static/svelte-build/favicon.svg +7 -0
- claude_mpm/dashboard/static/svelte-build/index.html +36 -0
- claude_mpm/dashboard-svelte/node_modules/katex/src/fonts/generate_fonts.py +58 -0
- claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/extract_tfms.py +114 -0
- claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/extract_ttfs.py +122 -0
- claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/format_json.py +28 -0
- claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/parse_tfm.py +211 -0
- claude_mpm/experimental/__init__.py +10 -0
- claude_mpm/experimental/cli_enhancements.py +104 -89
- claude_mpm/generators/__init__.py +1 -1
- claude_mpm/generators/agent_profile_generator.py +76 -66
- claude_mpm/hooks/__init__.py +37 -1
- claude_mpm/hooks/base_hook.py +37 -32
- claude_mpm/hooks/claude_hooks/__init__.py +1 -1
- claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/correlation_manager.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/connection_pool.py +250 -0
- claude_mpm/hooks/claude_hooks/correlation_manager.py +60 -0
- claude_mpm/hooks/claude_hooks/event_handlers.py +888 -0
- claude_mpm/hooks/claude_hooks/hook_handler.py +652 -875
- claude_mpm/hooks/claude_hooks/hook_wrapper.sh +10 -7
- claude_mpm/hooks/claude_hooks/installer.py +806 -0
- claude_mpm/hooks/claude_hooks/memory_integration.py +249 -0
- claude_mpm/hooks/claude_hooks/response_tracking.py +412 -0
- claude_mpm/hooks/claude_hooks/services/__init__.py +15 -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__/duplicate_detector.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +229 -0
- claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +254 -0
- claude_mpm/hooks/claude_hooks/services/duplicate_detector.py +106 -0
- claude_mpm/hooks/claude_hooks/services/state_manager.py +284 -0
- claude_mpm/hooks/claude_hooks/services/subagent_processor.py +374 -0
- claude_mpm/hooks/claude_hooks/tool_analysis.py +224 -0
- claude_mpm/hooks/failure_learning/__init__.py +54 -0
- claude_mpm/hooks/failure_learning/failure_detection_hook.py +230 -0
- claude_mpm/hooks/failure_learning/fix_detection_hook.py +212 -0
- claude_mpm/hooks/failure_learning/learning_extraction_hook.py +281 -0
- claude_mpm/hooks/instruction_reinforcement.py +301 -0
- claude_mpm/hooks/kuzu_enrichment_hook.py +263 -0
- claude_mpm/hooks/kuzu_memory_hook.py +386 -0
- claude_mpm/hooks/kuzu_response_hook.py +179 -0
- claude_mpm/hooks/memory_integration_hook.py +201 -107
- claude_mpm/hooks/session_resume_hook.py +121 -0
- claude_mpm/hooks/templates/pre_tool_use_simple.py +78 -0
- claude_mpm/hooks/templates/pre_tool_use_template.py +323 -0
- claude_mpm/hooks/tool_call_interceptor.py +92 -76
- claude_mpm/hooks/validation_hooks.py +62 -54
- claude_mpm/init.py +518 -83
- claude_mpm/models/__init__.py +9 -9
- claude_mpm/models/agent_definition.py +40 -23
- claude_mpm/models/agent_session.py +538 -0
- claude_mpm/models/git_repository.py +198 -0
- claude_mpm/models/resume_log.py +340 -0
- claude_mpm/schemas/__init__.py +12 -0
- claude_mpm/scripts/__init__.py +15 -0
- claude_mpm/scripts/claude-hook-handler.sh +227 -0
- claude_mpm/scripts/launch_monitor.py +165 -0
- claude_mpm/scripts/mpm_doctor.py +322 -0
- claude_mpm/scripts/socketio_daemon.py +189 -200
- claude_mpm/scripts/start_activity_logging.py +91 -0
- claude_mpm/services/__init__.py +208 -39
- claude_mpm/services/agent_capabilities_service.py +266 -0
- claude_mpm/services/agents/__init__.py +89 -0
- claude_mpm/services/agents/agent_builder.py +514 -0
- claude_mpm/services/agents/agent_preset_service.py +238 -0
- claude_mpm/services/agents/agent_recommendation_service.py +278 -0
- claude_mpm/services/agents/agent_review_service.py +280 -0
- claude_mpm/services/agents/agent_selection_service.py +484 -0
- claude_mpm/services/agents/auto_config_manager.py +796 -0
- claude_mpm/services/agents/auto_deploy_index_parser.py +569 -0
- claude_mpm/services/agents/cache_git_manager.py +621 -0
- claude_mpm/services/agents/deployment/__init__.py +21 -0
- claude_mpm/services/agents/deployment/agent_config_provider.py +410 -0
- claude_mpm/services/agents/deployment/agent_configuration_manager.py +358 -0
- claude_mpm/services/agents/deployment/agent_definition_factory.py +80 -0
- claude_mpm/services/agents/deployment/agent_deployment.py +1037 -0
- claude_mpm/services/agents/deployment/agent_discovery_service.py +546 -0
- claude_mpm/services/agents/deployment/agent_environment_manager.py +288 -0
- claude_mpm/services/agents/deployment/agent_filesystem_manager.py +383 -0
- claude_mpm/services/agents/deployment/agent_format_converter.py +505 -0
- claude_mpm/services/agents/deployment/agent_frontmatter_validator.py +160 -0
- claude_mpm/services/agents/deployment/agent_lifecycle_manager.py +957 -0
- claude_mpm/services/agents/deployment/agent_metrics_collector.py +273 -0
- claude_mpm/services/agents/deployment/agent_operation_service.py +573 -0
- claude_mpm/services/agents/deployment/agent_record_service.py +418 -0
- claude_mpm/services/agents/deployment/agent_restore_handler.py +84 -0
- claude_mpm/services/agents/deployment/agent_state_service.py +381 -0
- claude_mpm/services/agents/deployment/agent_template_builder.py +1369 -0
- claude_mpm/services/agents/deployment/agent_validator.py +376 -0
- claude_mpm/services/agents/deployment/agent_version_manager.py +322 -0
- claude_mpm/services/{agent_versioning.py → agents/deployment/agent_versioning.py} +10 -13
- claude_mpm/services/agents/deployment/agents_directory_resolver.py +149 -0
- claude_mpm/services/agents/deployment/async_agent_deployment.py +768 -0
- claude_mpm/services/agents/deployment/base_agent_locator.py +132 -0
- claude_mpm/services/agents/deployment/config/__init__.py +13 -0
- claude_mpm/services/agents/deployment/config/deployment_config.py +181 -0
- claude_mpm/services/agents/deployment/config/deployment_config_manager.py +200 -0
- claude_mpm/services/agents/deployment/deployment_config_loader.py +178 -0
- claude_mpm/services/agents/deployment/deployment_results_manager.py +185 -0
- claude_mpm/services/agents/deployment/deployment_type_detector.py +120 -0
- claude_mpm/services/agents/deployment/deployment_wrapper.py +129 -0
- claude_mpm/services/agents/deployment/facade/__init__.py +18 -0
- claude_mpm/services/agents/deployment/facade/async_deployment_executor.py +159 -0
- claude_mpm/services/agents/deployment/facade/deployment_executor.py +70 -0
- claude_mpm/services/agents/deployment/facade/deployment_facade.py +269 -0
- claude_mpm/services/agents/deployment/facade/sync_deployment_executor.py +178 -0
- claude_mpm/services/agents/deployment/interface_adapter.py +226 -0
- claude_mpm/services/agents/deployment/lifecycle_health_checker.py +85 -0
- claude_mpm/services/agents/deployment/lifecycle_performance_tracker.py +100 -0
- claude_mpm/services/agents/deployment/local_template_deployment.py +362 -0
- claude_mpm/services/agents/deployment/multi_source_deployment_service.py +1478 -0
- claude_mpm/services/agents/deployment/pipeline/__init__.py +32 -0
- claude_mpm/services/agents/deployment/pipeline/pipeline_builder.py +158 -0
- claude_mpm/services/agents/deployment/pipeline/pipeline_context.py +162 -0
- claude_mpm/services/agents/deployment/pipeline/pipeline_executor.py +169 -0
- claude_mpm/services/agents/deployment/pipeline/steps/__init__.py +19 -0
- claude_mpm/services/agents/deployment/pipeline/steps/agent_processing_step.py +240 -0
- claude_mpm/services/agents/deployment/pipeline/steps/base_step.py +110 -0
- claude_mpm/services/agents/deployment/pipeline/steps/configuration_step.py +80 -0
- claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +92 -0
- claude_mpm/services/agents/deployment/pipeline/steps/validation_step.py +101 -0
- claude_mpm/services/agents/deployment/processors/__init__.py +15 -0
- claude_mpm/services/agents/deployment/processors/agent_deployment_context.py +102 -0
- claude_mpm/services/agents/deployment/processors/agent_deployment_result.py +235 -0
- claude_mpm/services/agents/deployment/processors/agent_processor.py +269 -0
- claude_mpm/services/agents/deployment/refactored_agent_deployment_service.py +311 -0
- claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +862 -0
- claude_mpm/services/agents/deployment/results/__init__.py +13 -0
- claude_mpm/services/agents/deployment/results/deployment_metrics.py +200 -0
- claude_mpm/services/agents/deployment/results/deployment_result_builder.py +249 -0
- claude_mpm/services/agents/deployment/single_agent_deployer.py +315 -0
- claude_mpm/services/agents/deployment/strategies/__init__.py +25 -0
- claude_mpm/services/agents/deployment/strategies/base_strategy.py +113 -0
- claude_mpm/services/agents/deployment/strategies/project_strategy.py +148 -0
- claude_mpm/services/agents/deployment/strategies/strategy_selector.py +117 -0
- claude_mpm/services/agents/deployment/strategies/system_strategy.py +131 -0
- claude_mpm/services/agents/deployment/strategies/user_strategy.py +130 -0
- claude_mpm/services/agents/deployment/system_instructions_deployer.py +228 -0
- claude_mpm/services/agents/deployment/validation/__init__.py +21 -0
- claude_mpm/services/agents/deployment/validation/agent_validator.py +323 -0
- claude_mpm/services/agents/deployment/validation/deployment_validator.py +238 -0
- claude_mpm/services/agents/deployment/validation/template_validator.py +319 -0
- claude_mpm/services/agents/deployment/validation/validation_result.py +214 -0
- claude_mpm/services/agents/git_source_manager.py +682 -0
- claude_mpm/services/agents/loading/__init__.py +11 -0
- claude_mpm/services/{agent_profile_loader.py → agents/loading/agent_profile_loader.py} +306 -228
- claude_mpm/services/{base_agent_manager.py → agents/loading/base_agent_manager.py} +106 -91
- claude_mpm/services/agents/loading/framework_agent_loader.py +433 -0
- claude_mpm/services/agents/local_template_manager.py +784 -0
- claude_mpm/services/agents/management/__init__.py +9 -0
- claude_mpm/services/{agent_capabilities_generator.py → agents/management/agent_capabilities_generator.py} +92 -69
- claude_mpm/services/{agent_management_service.py → agents/management/agent_management_service.py} +219 -168
- claude_mpm/services/agents/memory/__init__.py +22 -0
- claude_mpm/services/agents/memory/agent_memory_manager.py +784 -0
- claude_mpm/services/{agent_persistence_service.py → agents/memory/agent_persistence_service.py} +20 -18
- claude_mpm/services/agents/memory/content_manager.py +470 -0
- claude_mpm/services/agents/memory/memory_categorization_service.py +167 -0
- claude_mpm/services/agents/memory/memory_file_service.py +129 -0
- claude_mpm/services/agents/memory/memory_format_service.py +201 -0
- claude_mpm/services/agents/memory/memory_limits_service.py +101 -0
- claude_mpm/services/agents/memory/template_generator.py +83 -0
- claude_mpm/services/agents/observers.py +547 -0
- claude_mpm/services/agents/recommender.py +617 -0
- claude_mpm/services/agents/registry/__init__.py +30 -0
- claude_mpm/services/agents/registry/deployed_agent_discovery.py +273 -0
- claude_mpm/services/{agent_modification_tracker.py → agents/registry/modification_tracker.py} +370 -295
- claude_mpm/services/agents/single_tier_deployment_service.py +696 -0
- claude_mpm/services/agents/sources/__init__.py +13 -0
- claude_mpm/services/agents/sources/agent_sync_state.py +516 -0
- claude_mpm/services/agents/sources/git_source_sync_service.py +1202 -0
- claude_mpm/services/agents/startup_sync.py +259 -0
- claude_mpm/services/agents/toolchain_detector.py +478 -0
- claude_mpm/services/analysis/__init__.py +35 -0
- claude_mpm/services/analysis/clone_detector.py +1030 -0
- claude_mpm/services/analysis/postmortem_reporter.py +474 -0
- claude_mpm/services/analysis/postmortem_service.py +765 -0
- claude_mpm/services/async_session_logger.py +665 -0
- claude_mpm/services/claude_session_logger.py +321 -0
- claude_mpm/services/cli/__init__.py +18 -0
- claude_mpm/services/cli/agent_cleanup_service.py +408 -0
- claude_mpm/services/cli/agent_dependency_service.py +395 -0
- claude_mpm/services/cli/agent_listing_service.py +463 -0
- claude_mpm/services/cli/agent_output_formatter.py +605 -0
- claude_mpm/services/cli/agent_validation_service.py +590 -0
- claude_mpm/services/cli/memory_crud_service.py +622 -0
- claude_mpm/services/cli/memory_output_formatter.py +604 -0
- claude_mpm/services/cli/resume_service.py +617 -0
- claude_mpm/services/cli/session_manager.py +604 -0
- claude_mpm/services/cli/session_pause_manager.py +504 -0
- claude_mpm/services/cli/session_resume_helper.py +372 -0
- claude_mpm/services/cli/startup_checker.py +362 -0
- claude_mpm/services/cli/unified_dashboard_manager.py +439 -0
- claude_mpm/services/command_deployment_service.py +446 -0
- claude_mpm/services/command_handler_service.py +221 -0
- claude_mpm/services/communication/__init__.py +22 -0
- claude_mpm/services/core/__init__.py +108 -0
- claude_mpm/services/core/base.py +269 -0
- claude_mpm/services/core/cache_manager.py +309 -0
- claude_mpm/services/core/interfaces/__init__.py +273 -0
- claude_mpm/services/core/interfaces/agent.py +514 -0
- claude_mpm/services/core/interfaces/communication.py +316 -0
- claude_mpm/services/core/interfaces/health.py +169 -0
- claude_mpm/services/core/interfaces/infrastructure.py +357 -0
- claude_mpm/services/core/interfaces/model.py +281 -0
- claude_mpm/services/core/interfaces/process.py +372 -0
- claude_mpm/services/core/interfaces/project.py +121 -0
- claude_mpm/services/core/interfaces/restart.py +307 -0
- claude_mpm/services/core/interfaces/service.py +405 -0
- claude_mpm/services/core/interfaces/stability.py +260 -0
- claude_mpm/services/core/interfaces.py +81 -0
- claude_mpm/services/core/memory_manager.py +682 -0
- claude_mpm/services/core/models/__init__.py +70 -0
- claude_mpm/services/core/models/agent_config.py +384 -0
- claude_mpm/services/core/models/health.py +162 -0
- claude_mpm/services/core/models/process.py +239 -0
- claude_mpm/services/core/models/restart.py +302 -0
- claude_mpm/services/core/models/stability.py +264 -0
- claude_mpm/services/core/models/toolchain.py +306 -0
- claude_mpm/services/core/path_resolver.py +517 -0
- claude_mpm/services/core/service_container.py +520 -0
- claude_mpm/services/core/service_interfaces.py +436 -0
- claude_mpm/services/diagnostics/__init__.py +18 -0
- claude_mpm/services/diagnostics/checks/__init__.py +38 -0
- claude_mpm/services/diagnostics/checks/agent_check.py +370 -0
- claude_mpm/services/diagnostics/checks/agent_sources_check.py +577 -0
- claude_mpm/services/diagnostics/checks/base_check.py +60 -0
- claude_mpm/services/diagnostics/checks/claude_code_check.py +270 -0
- claude_mpm/services/diagnostics/checks/common_issues_check.py +363 -0
- claude_mpm/services/diagnostics/checks/configuration_check.py +306 -0
- claude_mpm/services/diagnostics/checks/filesystem_check.py +233 -0
- claude_mpm/services/diagnostics/checks/installation_check.py +520 -0
- claude_mpm/services/diagnostics/checks/instructions_check.py +415 -0
- claude_mpm/services/diagnostics/checks/mcp_check.py +330 -0
- claude_mpm/services/diagnostics/checks/mcp_services_check.py +1058 -0
- claude_mpm/services/diagnostics/checks/monitor_check.py +281 -0
- claude_mpm/services/diagnostics/checks/skill_sources_check.py +587 -0
- claude_mpm/services/diagnostics/checks/startup_log_check.py +319 -0
- claude_mpm/services/diagnostics/diagnostic_runner.py +286 -0
- claude_mpm/services/diagnostics/doctor_reporter.py +578 -0
- claude_mpm/services/diagnostics/models.py +138 -0
- claude_mpm/services/event_aggregator.py +582 -0
- claude_mpm/services/event_bus/__init__.py +18 -0
- claude_mpm/services/event_bus/config.py +186 -0
- claude_mpm/services/event_bus/direct_relay.py +312 -0
- claude_mpm/services/event_bus/event_bus.py +396 -0
- claude_mpm/services/event_bus/relay.py +326 -0
- claude_mpm/services/events/__init__.py +44 -0
- claude_mpm/services/events/consumers/__init__.py +18 -0
- claude_mpm/services/events/consumers/dead_letter.py +306 -0
- claude_mpm/services/events/consumers/logging.py +184 -0
- claude_mpm/services/events/consumers/metrics.py +241 -0
- claude_mpm/services/events/consumers/socketio.py +377 -0
- claude_mpm/services/events/core.py +480 -0
- claude_mpm/services/events/interfaces.py +214 -0
- claude_mpm/services/events/producers/__init__.py +14 -0
- claude_mpm/services/events/producers/hook.py +269 -0
- claude_mpm/services/events/producers/system.py +329 -0
- claude_mpm/services/exceptions.py +433 -353
- claude_mpm/services/framework_claude_md_generator/__init__.py +81 -80
- claude_mpm/services/framework_claude_md_generator/content_assembler.py +74 -67
- claude_mpm/services/framework_claude_md_generator/content_validator.py +66 -62
- claude_mpm/services/framework_claude_md_generator/deployment_manager.py +82 -60
- claude_mpm/services/framework_claude_md_generator/section_generators/__init__.py +36 -37
- claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +41 -40
- claude_mpm/services/framework_claude_md_generator/section_generators/claude_pm_init.py +15 -15
- claude_mpm/services/framework_claude_md_generator/section_generators/core_responsibilities.py +5 -4
- claude_mpm/services/framework_claude_md_generator/section_generators/delegation_constraints.py +4 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/environment_config.py +4 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/footer.py +6 -5
- claude_mpm/services/framework_claude_md_generator/section_generators/header.py +8 -7
- claude_mpm/services/framework_claude_md_generator/section_generators/orchestration_principles.py +5 -4
- claude_mpm/services/framework_claude_md_generator/section_generators/role_designation.py +6 -5
- claude_mpm/services/framework_claude_md_generator/section_generators/subprocess_validation.py +9 -8
- claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +26 -30
- claude_mpm/services/framework_claude_md_generator/section_generators/troubleshooting.py +6 -5
- claude_mpm/services/framework_claude_md_generator/section_manager.py +28 -27
- claude_mpm/services/framework_claude_md_generator/version_manager.py +31 -30
- claude_mpm/services/git/__init__.py +21 -0
- claude_mpm/services/git/git_operations_service.py +579 -0
- claude_mpm/services/github/__init__.py +21 -0
- claude_mpm/services/github/github_cli_service.py +397 -0
- claude_mpm/services/hook_installer_service.py +506 -0
- claude_mpm/services/hook_service.py +159 -111
- claude_mpm/services/infrastructure/__init__.py +52 -0
- claude_mpm/services/infrastructure/context_preservation.py +569 -0
- claude_mpm/services/infrastructure/daemon_manager.py +279 -0
- claude_mpm/services/infrastructure/logging.py +209 -0
- claude_mpm/services/infrastructure/monitoring/__init__.py +39 -0
- claude_mpm/services/infrastructure/monitoring/aggregator.py +432 -0
- claude_mpm/services/infrastructure/monitoring/base.py +122 -0
- claude_mpm/services/infrastructure/monitoring/legacy.py +203 -0
- claude_mpm/services/infrastructure/monitoring/network.py +219 -0
- claude_mpm/services/infrastructure/monitoring/process.py +343 -0
- claude_mpm/services/infrastructure/monitoring/resources.py +244 -0
- claude_mpm/services/infrastructure/monitoring/service.py +368 -0
- claude_mpm/services/infrastructure/monitoring.py +71 -0
- claude_mpm/services/infrastructure/resume_log_generator.py +439 -0
- claude_mpm/services/instructions/__init__.py +9 -0
- claude_mpm/services/instructions/instruction_cache_service.py +374 -0
- claude_mpm/services/local_ops/__init__.py +155 -0
- claude_mpm/services/local_ops/crash_detector.py +257 -0
- claude_mpm/services/local_ops/health_checks/__init__.py +26 -0
- claude_mpm/services/local_ops/health_checks/http_check.py +224 -0
- claude_mpm/services/local_ops/health_checks/process_check.py +236 -0
- claude_mpm/services/local_ops/health_checks/resource_check.py +255 -0
- claude_mpm/services/local_ops/health_manager.py +427 -0
- claude_mpm/services/local_ops/log_monitor.py +396 -0
- claude_mpm/services/local_ops/memory_leak_detector.py +294 -0
- claude_mpm/services/local_ops/process_manager.py +595 -0
- claude_mpm/services/local_ops/resource_monitor.py +331 -0
- claude_mpm/services/local_ops/restart_manager.py +401 -0
- claude_mpm/services/local_ops/restart_policy.py +387 -0
- claude_mpm/services/local_ops/state_manager.py +372 -0
- claude_mpm/services/local_ops/unified_manager.py +600 -0
- claude_mpm/services/mcp_config_manager.py +1542 -0
- claude_mpm/services/mcp_service_verifier.py +732 -0
- claude_mpm/services/memory/__init__.py +19 -0
- claude_mpm/services/{memory_builder.py → memory/builder.py} +465 -373
- claude_mpm/services/memory/cache/__init__.py +14 -0
- claude_mpm/services/{shared_prompt_cache.py → memory/cache/shared_prompt_cache.py} +237 -200
- claude_mpm/services/memory/cache/simple_cache.py +331 -0
- claude_mpm/services/memory/failure_tracker.py +578 -0
- claude_mpm/services/memory/indexed_memory.py +648 -0
- claude_mpm/services/{memory_optimizer.py → memory/optimizer.py} +272 -243
- claude_mpm/services/memory/router.py +951 -0
- claude_mpm/services/memory_hook_service.py +470 -0
- claude_mpm/services/model/__init__.py +147 -0
- claude_mpm/services/model/base_provider.py +365 -0
- claude_mpm/services/model/claude_provider.py +412 -0
- claude_mpm/services/model/model_router.py +452 -0
- claude_mpm/services/model/ollama_provider.py +415 -0
- claude_mpm/services/monitor/__init__.py +20 -0
- claude_mpm/services/monitor/daemon.py +698 -0
- claude_mpm/services/monitor/daemon_manager.py +1076 -0
- claude_mpm/services/monitor/event_emitter.py +350 -0
- claude_mpm/services/monitor/handlers/__init__.py +21 -0
- claude_mpm/services/monitor/handlers/code_analysis.py +332 -0
- claude_mpm/services/monitor/handlers/dashboard.py +299 -0
- claude_mpm/services/monitor/handlers/file.py +264 -0
- claude_mpm/services/monitor/handlers/hooks.py +512 -0
- claude_mpm/services/monitor/management/__init__.py +18 -0
- claude_mpm/services/monitor/management/health.py +124 -0
- claude_mpm/services/monitor/management/lifecycle.py +730 -0
- claude_mpm/services/monitor/server.py +1493 -0
- claude_mpm/services/monitor_build_service.py +349 -0
- claude_mpm/services/native_agent_converter.py +356 -0
- claude_mpm/services/orphan_detection.py +786 -0
- claude_mpm/services/pm_skills_deployer.py +707 -0
- claude_mpm/services/port_manager.py +597 -0
- claude_mpm/services/pr/__init__.py +14 -0
- claude_mpm/services/pr/pr_template_service.py +329 -0
- claude_mpm/services/profile_manager.py +337 -0
- claude_mpm/services/project/__init__.py +44 -0
- claude_mpm/services/{project_analyzer.py → project/analyzer.py} +541 -291
- claude_mpm/services/project/analyzer_v2.py +566 -0
- claude_mpm/services/project/architecture_analyzer.py +461 -0
- claude_mpm/services/project/archive_manager.py +1045 -0
- claude_mpm/services/project/dependency_analyzer.py +462 -0
- claude_mpm/services/project/detection_strategies.py +719 -0
- claude_mpm/services/project/documentation_manager.py +554 -0
- claude_mpm/services/project/enhanced_analyzer.py +572 -0
- claude_mpm/services/project/language_analyzer.py +265 -0
- claude_mpm/services/project/metrics_collector.py +407 -0
- claude_mpm/services/project/project_organizer.py +1009 -0
- claude_mpm/services/project/registry.py +636 -0
- claude_mpm/services/project/toolchain_analyzer.py +583 -0
- claude_mpm/services/project_port_allocator.py +596 -0
- claude_mpm/services/recovery_manager.py +293 -240
- claude_mpm/services/response_tracker.py +267 -0
- claude_mpm/services/runner_configuration_service.py +605 -0
- claude_mpm/services/self_upgrade_service.py +608 -0
- claude_mpm/services/session_management_service.py +314 -0
- claude_mpm/services/session_manager.py +380 -0
- claude_mpm/services/shared/__init__.py +21 -0
- claude_mpm/services/shared/async_service_base.py +216 -0
- claude_mpm/services/shared/config_service_base.py +301 -0
- claude_mpm/services/shared/lifecycle_service_base.py +308 -0
- claude_mpm/services/shared/manager_base.py +315 -0
- claude_mpm/services/shared/service_factory.py +309 -0
- claude_mpm/services/skills/__init__.py +21 -0
- claude_mpm/services/skills/git_skill_source_manager.py +1324 -0
- claude_mpm/services/skills/selective_skill_deployer.py +744 -0
- claude_mpm/services/skills/skill_discovery_service.py +568 -0
- claude_mpm/services/skills/skill_to_agent_mapper.py +406 -0
- claude_mpm/services/skills_config.py +547 -0
- claude_mpm/services/skills_deployer.py +1168 -0
- claude_mpm/services/socketio/__init__.py +25 -0
- claude_mpm/services/socketio/client_proxy.py +229 -0
- claude_mpm/services/socketio/dashboard_server.py +362 -0
- claude_mpm/services/socketio/event_normalizer.py +798 -0
- claude_mpm/services/socketio/handlers/__init__.py +30 -0
- claude_mpm/services/socketio/handlers/base.py +136 -0
- claude_mpm/services/socketio/handlers/code_analysis.py +682 -0
- claude_mpm/services/socketio/handlers/connection.py +643 -0
- claude_mpm/services/socketio/handlers/connection_handler.py +333 -0
- claude_mpm/services/socketio/handlers/file.py +263 -0
- claude_mpm/services/socketio/handlers/git.py +962 -0
- claude_mpm/services/socketio/handlers/hook.py +211 -0
- claude_mpm/services/socketio/handlers/memory.py +26 -0
- claude_mpm/services/socketio/handlers/project.py +24 -0
- claude_mpm/services/socketio/handlers/registry.py +214 -0
- claude_mpm/services/socketio/migration_utils.py +343 -0
- claude_mpm/services/socketio/monitor_client.py +364 -0
- claude_mpm/services/socketio/server/__init__.py +18 -0
- claude_mpm/services/socketio/server/broadcaster.py +569 -0
- claude_mpm/services/socketio/server/connection_manager.py +579 -0
- claude_mpm/services/socketio/server/core.py +1079 -0
- claude_mpm/services/socketio/server/eventbus_integration.py +245 -0
- claude_mpm/services/socketio/server/main.py +501 -0
- claude_mpm/services/socketio_client_manager.py +173 -143
- claude_mpm/services/socketio_server.py +38 -1657
- claude_mpm/services/subprocess_launcher_service.py +322 -0
- claude_mpm/services/system_instructions_service.py +270 -0
- claude_mpm/services/ticket_manager.py +25 -209
- claude_mpm/services/ticket_services/__init__.py +26 -0
- claude_mpm/services/ticket_services/crud_service.py +328 -0
- claude_mpm/services/ticket_services/formatter_service.py +290 -0
- claude_mpm/services/ticket_services/search_service.py +324 -0
- claude_mpm/services/ticket_services/validation_service.py +303 -0
- claude_mpm/services/ticket_services/workflow_service.py +244 -0
- claude_mpm/services/unified/__init__.py +65 -0
- claude_mpm/services/unified/analyzer_strategies/__init__.py +44 -0
- claude_mpm/services/unified/analyzer_strategies/code_analyzer.py +518 -0
- claude_mpm/services/unified/analyzer_strategies/dependency_analyzer.py +680 -0
- claude_mpm/services/unified/analyzer_strategies/performance_analyzer.py +900 -0
- claude_mpm/services/unified/analyzer_strategies/security_analyzer.py +745 -0
- claude_mpm/services/unified/analyzer_strategies/structure_analyzer.py +733 -0
- claude_mpm/services/unified/config_strategies/__init__.py +175 -0
- claude_mpm/services/unified/config_strategies/config_schema.py +731 -0
- claude_mpm/services/unified/config_strategies/context_strategy.py +747 -0
- claude_mpm/services/unified/config_strategies/error_handling_strategy.py +1005 -0
- claude_mpm/services/unified/config_strategies/file_loader_strategy.py +881 -0
- claude_mpm/services/unified/config_strategies/unified_config_service.py +823 -0
- claude_mpm/services/unified/config_strategies/validation_strategy.py +1148 -0
- claude_mpm/services/unified/deployment_strategies/__init__.py +97 -0
- claude_mpm/services/unified/deployment_strategies/base.py +553 -0
- claude_mpm/services/unified/deployment_strategies/cloud_strategies.py +573 -0
- claude_mpm/services/unified/deployment_strategies/local.py +607 -0
- claude_mpm/services/unified/deployment_strategies/utils.py +667 -0
- claude_mpm/services/unified/deployment_strategies/vercel.py +471 -0
- claude_mpm/services/unified/interfaces.py +475 -0
- claude_mpm/services/unified/migration.py +509 -0
- claude_mpm/services/unified/strategies.py +534 -0
- claude_mpm/services/unified/unified_analyzer.py +542 -0
- claude_mpm/services/unified/unified_config.py +691 -0
- claude_mpm/services/unified/unified_deployment.py +466 -0
- claude_mpm/services/utility_service.py +280 -0
- claude_mpm/services/version_control/__init__.py +34 -37
- claude_mpm/services/version_control/branch_strategy.py +26 -17
- claude_mpm/services/version_control/conflict_resolution.py +52 -36
- claude_mpm/services/version_control/git_operations.py +183 -49
- claude_mpm/services/version_control/semantic_versioning.py +172 -61
- claude_mpm/services/version_control/version_parser.py +546 -0
- claude_mpm/services/version_service.py +379 -0
- claude_mpm/services/visualization/__init__.py +15 -0
- claude_mpm/services/visualization/mermaid_generator.py +937 -0
- claude_mpm/skills/__init__.py +42 -0
- claude_mpm/skills/agent_skills_injector.py +324 -0
- claude_mpm/skills/bundled/LICENSE_ATTRIBUTIONS.md +79 -0
- claude_mpm/skills/bundled/__init__.py +6 -0
- claude_mpm/skills/bundled/api-documentation.md +393 -0
- claude_mpm/skills/bundled/async-testing.md +571 -0
- claude_mpm/skills/bundled/code-review.md +143 -0
- claude_mpm/skills/bundled/database-migration.md +199 -0
- claude_mpm/skills/bundled/docker-containerization.md +194 -0
- claude_mpm/skills/bundled/express-local-dev.md +1429 -0
- claude_mpm/skills/bundled/fastapi-local-dev.md +1199 -0
- claude_mpm/skills/bundled/git-workflow.md +414 -0
- claude_mpm/skills/bundled/imagemagick.md +204 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/scripts/validate_env.py +576 -0
- claude_mpm/skills/bundled/json-data-handling.md +223 -0
- claude_mpm/skills/bundled/main/mcp-builder/scripts/connections.py +157 -0
- claude_mpm/skills/bundled/main/mcp-builder/scripts/evaluation.py +425 -0
- claude_mpm/skills/bundled/main/skill-creator/scripts/init_skill.py +303 -0
- claude_mpm/skills/bundled/main/skill-creator/scripts/package_skill.py +113 -0
- claude_mpm/skills/bundled/main/skill-creator/scripts/quick_validate.py +72 -0
- claude_mpm/skills/bundled/nextjs-local-dev.md +807 -0
- claude_mpm/skills/bundled/pdf.md +141 -0
- claude_mpm/skills/bundled/performance-profiling.md +573 -0
- claude_mpm/skills/bundled/refactoring-patterns.md +180 -0
- claude_mpm/skills/bundled/security-scanning.md +439 -0
- claude_mpm/skills/bundled/systematic-debugging.md +473 -0
- claude_mpm/skills/bundled/test-driven-development.md +378 -0
- claude_mpm/skills/bundled/testing/webapp-testing/examples/console_logging.py +35 -0
- claude_mpm/skills/bundled/testing/webapp-testing/examples/element_discovery.py +44 -0
- claude_mpm/skills/bundled/testing/webapp-testing/examples/static_html_automation.py +34 -0
- claude_mpm/skills/bundled/testing/webapp-testing/scripts/with_server.py +129 -0
- claude_mpm/skills/bundled/vite-local-dev.md +1061 -0
- claude_mpm/skills/bundled/web-performance-optimization.md +2305 -0
- claude_mpm/skills/bundled/xlsx.md +157 -0
- claude_mpm/skills/registry.py +286 -0
- claude_mpm/skills/skill_manager.py +405 -0
- claude_mpm/skills/skills_registry.py +347 -0
- claude_mpm/skills/skills_service.py +739 -0
- claude_mpm/storage/__init__.py +9 -0
- claude_mpm/storage/state_storage.py +546 -0
- claude_mpm/templates/.pre-commit-config.yaml +112 -0
- claude_mpm/templates/questions/__init__.py +38 -0
- claude_mpm/templates/questions/base.py +193 -0
- claude_mpm/templates/questions/pr_strategy.py +311 -0
- claude_mpm/templates/questions/project_init.py +385 -0
- claude_mpm/templates/questions/ticket_mgmt.py +394 -0
- claude_mpm/ticket_wrapper.py +2 -2
- claude_mpm/tools/__init__.py +10 -0
- claude_mpm/tools/__main__.py +208 -0
- claude_mpm/tools/code_tree_analyzer/__init__.py +45 -0
- claude_mpm/tools/code_tree_analyzer/analysis.py +299 -0
- claude_mpm/tools/code_tree_analyzer/cache.py +131 -0
- claude_mpm/tools/code_tree_analyzer/core.py +380 -0
- claude_mpm/tools/code_tree_analyzer/discovery.py +403 -0
- claude_mpm/tools/code_tree_analyzer/events.py +168 -0
- claude_mpm/tools/code_tree_analyzer/gitignore.py +308 -0
- claude_mpm/tools/code_tree_analyzer/models.py +39 -0
- claude_mpm/tools/code_tree_analyzer/multilang_analyzer.py +224 -0
- claude_mpm/tools/code_tree_analyzer/python_analyzer.py +284 -0
- claude_mpm/tools/code_tree_builder.py +631 -0
- claude_mpm/tools/code_tree_events.py +420 -0
- claude_mpm/tools/socketio_debug.py +671 -0
- claude_mpm/utils/__init__.py +8 -8
- claude_mpm/utils/agent_dependency_loader.py +1090 -0
- claude_mpm/utils/agent_filters.py +261 -0
- claude_mpm/utils/common.py +544 -0
- claude_mpm/utils/config_manager.py +168 -126
- claude_mpm/utils/console.py +11 -0
- claude_mpm/utils/database_connector.py +298 -0
- claude_mpm/utils/dependency_cache.py +373 -0
- claude_mpm/utils/dependency_manager.py +60 -59
- claude_mpm/utils/dependency_strategies.py +381 -0
- claude_mpm/utils/display_helper.py +260 -0
- claude_mpm/utils/environment_context.py +313 -0
- claude_mpm/utils/error_handler.py +78 -66
- claude_mpm/utils/file_utils.py +305 -0
- claude_mpm/utils/framework_detection.py +12 -11
- claude_mpm/utils/git_analyzer.py +407 -0
- claude_mpm/utils/gitignore.py +244 -0
- claude_mpm/utils/import_migration_example.py +12 -60
- claude_mpm/utils/imports.py +48 -45
- claude_mpm/utils/log_cleanup.py +627 -0
- claude_mpm/utils/migration.py +372 -0
- claude_mpm/utils/path_operations.py +110 -104
- claude_mpm/utils/progress.py +387 -0
- claude_mpm/utils/robust_installer.py +823 -0
- claude_mpm/utils/session_logging.py +121 -0
- claude_mpm/utils/structured_questions.py +619 -0
- claude_mpm/utils/subprocess_utils.py +343 -0
- claude_mpm/validation/__init__.py +1 -1
- claude_mpm/validation/agent_validator.py +214 -108
- claude_mpm/validation/frontmatter_validator.py +252 -0
- claude_mpm-5.4.55.dist-info/METADATA +999 -0
- claude_mpm-5.4.55.dist-info/RECORD +868 -0
- {claude_mpm-3.4.10.dist-info → claude_mpm-5.4.55.dist-info}/entry_points.txt +1 -3
- claude_mpm-5.4.55.dist-info/licenses/LICENSE +94 -0
- claude_mpm-5.4.55.dist-info/licenses/LICENSE-FAQ.md +153 -0
- claude_mpm/agents/BASE_AGENT_TEMPLATE.md +0 -88
- claude_mpm/agents/INSTRUCTIONS.md +0 -352
- claude_mpm/agents/backups/INSTRUCTIONS.md +0 -352
- claude_mpm/agents/base_agent_loader.py +0 -529
- claude_mpm/agents/schema/agent_schema.json +0 -314
- claude_mpm/agents/templates/.claude-mpm/memories/README.md +0 -36
- claude_mpm/agents/templates/backup/data_engineer_agent_20250726_234551.json +0 -46
- claude_mpm/agents/templates/backup/documentation_agent_20250726_234551.json +0 -45
- claude_mpm/agents/templates/backup/engineer_agent_20250726_234551.json +0 -49
- claude_mpm/agents/templates/backup/ops_agent_20250726_234551.json +0 -46
- claude_mpm/agents/templates/backup/qa_agent_20250726_234551.json +0 -45
- claude_mpm/agents/templates/backup/research_agent_20250726_234551.json +0 -49
- claude_mpm/agents/templates/backup/security_agent_20250726_234551.json +0 -46
- claude_mpm/agents/templates/backup/version_control_agent_20250726_234551.json +0 -46
- claude_mpm/agents/templates/data_engineer.json +0 -110
- claude_mpm/agents/templates/documentation.json +0 -109
- claude_mpm/agents/templates/engineer.json +0 -113
- claude_mpm/agents/templates/ops.json +0 -109
- claude_mpm/agents/templates/pm.json +0 -25
- claude_mpm/agents/templates/qa.json +0 -111
- claude_mpm/agents/templates/research.json +0 -65
- claude_mpm/agents/templates/security.json +0 -113
- claude_mpm/agents/templates/test_integration.json +0 -112
- claude_mpm/agents/templates/version_control.json +0 -107
- claude_mpm/cli/commands/ui.py +0 -57
- claude_mpm/core/simple_runner.py +0 -1046
- claude_mpm/dashboard/open_dashboard.py +0 -34
- claude_mpm/deployment_paths.py +0 -261
- claude_mpm/hooks/builtin/__init__.py +0 -1
- claude_mpm/hooks/builtin/logging_hook_example.py +0 -165
- claude_mpm/hooks/builtin/memory_hooks_example.py +0 -67
- claude_mpm/hooks/builtin/mpm_command_hook.py +0 -125
- claude_mpm/hooks/builtin/post_delegation_hook_example.py +0 -124
- claude_mpm/hooks/builtin/pre_delegation_hook_example.py +0 -125
- claude_mpm/hooks/builtin/submit_hook_example.py +0 -100
- claude_mpm/hooks/builtin/ticket_extraction_hook_example.py +0 -237
- claude_mpm/hooks/builtin/todo_agent_prefix_hook.py +0 -240
- claude_mpm/hooks/builtin/workflow_start_hook.py +0 -181
- claude_mpm/orchestration/__init__.py +0 -6
- claude_mpm/orchestration/archive/direct_orchestrator.py +0 -195
- claude_mpm/orchestration/archive/factory.py +0 -215
- claude_mpm/orchestration/archive/hook_enabled_orchestrator.py +0 -188
- claude_mpm/orchestration/archive/hook_integration_example.py +0 -178
- claude_mpm/orchestration/archive/interactive_subprocess_orchestrator.py +0 -826
- claude_mpm/orchestration/archive/orchestrator.py +0 -501
- claude_mpm/orchestration/archive/pexpect_orchestrator.py +0 -252
- claude_mpm/orchestration/archive/pty_orchestrator.py +0 -270
- claude_mpm/orchestration/archive/simple_orchestrator.py +0 -82
- claude_mpm/orchestration/archive/subprocess_orchestrator.py +0 -801
- claude_mpm/orchestration/archive/system_prompt_orchestrator.py +0 -278
- claude_mpm/orchestration/archive/wrapper_orchestrator.py +0 -187
- claude_mpm/schemas/workflow_validator.py +0 -411
- claude_mpm/services/agent_deployment.py +0 -1534
- claude_mpm/services/agent_lifecycle_manager.py +0 -1169
- claude_mpm/services/agent_memory_manager.py +0 -1415
- claude_mpm/services/agent_registry.py +0 -676
- claude_mpm/services/deployed_agent_discovery.py +0 -226
- claude_mpm/services/framework_agent_loader.py +0 -337
- claude_mpm/services/framework_claude_md_generator.py +0 -621
- claude_mpm/services/health_monitor.py +0 -892
- claude_mpm/services/memory_router.py +0 -538
- claude_mpm/services/parent_directory_manager/__init__.py +0 -577
- claude_mpm/services/parent_directory_manager/backup_manager.py +0 -258
- claude_mpm/services/parent_directory_manager/config_manager.py +0 -210
- claude_mpm/services/parent_directory_manager/deduplication_manager.py +0 -279
- claude_mpm/services/parent_directory_manager/framework_protector.py +0 -143
- claude_mpm/services/parent_directory_manager/operations.py +0 -186
- claude_mpm/services/parent_directory_manager/state_manager.py +0 -624
- claude_mpm/services/parent_directory_manager/template_deployer.py +0 -579
- claude_mpm/services/parent_directory_manager/validation_manager.py +0 -378
- claude_mpm/services/parent_directory_manager/version_control_helper.py +0 -339
- claude_mpm/services/parent_directory_manager/version_manager.py +0 -222
- claude_mpm/services/standalone_socketio_server.py +0 -1300
- claude_mpm/services/ticket_manager_di.py +0 -318
- claude_mpm/services/ticketing_service_original.py +0 -508
- claude_mpm/ui/__init__.py +0 -1
- claude_mpm/ui/rich_terminal_ui.py +0 -295
- claude_mpm/ui/terminal_ui.py +0 -328
- claude_mpm/utils/paths.py +0 -289
- claude_mpm-3.4.10.dist-info/METADATA +0 -183
- claude_mpm-3.4.10.dist-info/RECORD +0 -201
- claude_mpm-3.4.10.dist-info/licenses/LICENSE +0 -21
- {claude_mpm-3.4.10.dist-info → claude_mpm-5.4.55.dist-info}/WHEEL +0 -0
- {claude_mpm-3.4.10.dist-info → claude_mpm-5.4.55.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,1478 @@
|
|
|
1
|
+
"""Multi-Source Agent Deployment Service
|
|
2
|
+
|
|
3
|
+
This service implements proper version comparison across multiple agent sources,
|
|
4
|
+
ensuring the highest version agent is deployed regardless of source.
|
|
5
|
+
|
|
6
|
+
Key Features:
|
|
7
|
+
- Discovers agents from multiple sources (system templates, project agents, user agents)
|
|
8
|
+
- Compares versions across all sources
|
|
9
|
+
- Deploys the highest version for each agent
|
|
10
|
+
- Tracks which source provided the deployed agent
|
|
11
|
+
- Maintains backward compatibility with existing deployment modes
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import json
|
|
15
|
+
import os
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
18
|
+
|
|
19
|
+
import yaml
|
|
20
|
+
|
|
21
|
+
from claude_mpm.core.config import Config
|
|
22
|
+
from claude_mpm.core.logging_config import get_logger
|
|
23
|
+
|
|
24
|
+
from .agent_discovery_service import AgentDiscoveryService
|
|
25
|
+
from .agent_version_manager import AgentVersionManager
|
|
26
|
+
from .remote_agent_discovery_service import RemoteAgentDiscoveryService
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _normalize_agent_name(name: str) -> str:
|
|
30
|
+
"""Normalize agent name for consistent comparison.
|
|
31
|
+
|
|
32
|
+
Converts spaces, underscores to hyphens and lowercases.
|
|
33
|
+
Examples:
|
|
34
|
+
"Dart Engineer" -> "dart-engineer"
|
|
35
|
+
"dart_engineer" -> "dart-engineer"
|
|
36
|
+
"DART-ENGINEER" -> "dart-engineer"
|
|
37
|
+
"""
|
|
38
|
+
return name.lower().replace(" ", "-").replace("_", "-")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class MultiSourceAgentDeploymentService:
|
|
42
|
+
"""Service for deploying agents from multiple sources with version comparison.
|
|
43
|
+
|
|
44
|
+
This service ensures that the highest version of each agent is deployed,
|
|
45
|
+
regardless of whether it comes from system templates, project agents,
|
|
46
|
+
user agents, or remote agents.
|
|
47
|
+
|
|
48
|
+
4-Tier Agent Discovery:
|
|
49
|
+
1. System templates (lowest priority) - Built-in agents
|
|
50
|
+
2. User agents (DEPRECATED) - User-level customizations (~/.claude-mpm/agents/)
|
|
51
|
+
3. Remote agents - Agents cached from GitHub
|
|
52
|
+
4. Project agents (highest priority) - Project-specific customizations
|
|
53
|
+
|
|
54
|
+
WHY: The current system processes agents from a single source at a time,
|
|
55
|
+
which can result in lower version agents being deployed if they exist in
|
|
56
|
+
a higher priority source. This service fixes that by comparing versions
|
|
57
|
+
across all sources.
|
|
58
|
+
|
|
59
|
+
DEPRECATION: User-level agents (~/.claude-mpm/agents/) are deprecated and
|
|
60
|
+
will be removed in v5.0.0. Use project-level agents instead.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
def __init__(self):
|
|
64
|
+
"""Initialize the multi-source deployment service."""
|
|
65
|
+
self.logger = get_logger(__name__)
|
|
66
|
+
self.version_manager = AgentVersionManager()
|
|
67
|
+
|
|
68
|
+
def _read_template_version(self, template_path: Path) -> Optional[str]:
|
|
69
|
+
"""Read version from template file (supports both .md and .json formats).
|
|
70
|
+
|
|
71
|
+
For .md files: Extract version from YAML frontmatter
|
|
72
|
+
For .json files: Extract version from JSON structure
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
template_path: Path to template file
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
Version string or None if version cannot be extracted
|
|
79
|
+
"""
|
|
80
|
+
try:
|
|
81
|
+
if template_path.suffix == ".md":
|
|
82
|
+
# Parse markdown with YAML frontmatter
|
|
83
|
+
content = template_path.read_text()
|
|
84
|
+
|
|
85
|
+
# Extract YAML frontmatter (between --- markers)
|
|
86
|
+
if not content.strip().startswith("---"):
|
|
87
|
+
return None
|
|
88
|
+
|
|
89
|
+
parts = content.split("---", 2)
|
|
90
|
+
if len(parts) < 3:
|
|
91
|
+
return None
|
|
92
|
+
|
|
93
|
+
# Parse YAML frontmatter
|
|
94
|
+
frontmatter = yaml.safe_load(parts[1])
|
|
95
|
+
if not frontmatter:
|
|
96
|
+
return None
|
|
97
|
+
|
|
98
|
+
# Extract version from frontmatter
|
|
99
|
+
version = frontmatter.get("version")
|
|
100
|
+
return version if version else None
|
|
101
|
+
|
|
102
|
+
if template_path.suffix == ".json":
|
|
103
|
+
# Parse JSON template
|
|
104
|
+
template_data = json.loads(template_path.read_text())
|
|
105
|
+
metadata = template_data.get("metadata", {})
|
|
106
|
+
version = (
|
|
107
|
+
template_data.get("agent_version")
|
|
108
|
+
or template_data.get("version")
|
|
109
|
+
or metadata.get("version")
|
|
110
|
+
)
|
|
111
|
+
return version if version else None
|
|
112
|
+
|
|
113
|
+
self.logger.warning(
|
|
114
|
+
f"Unknown template format: {template_path.suffix} for {template_path.name}"
|
|
115
|
+
)
|
|
116
|
+
return None
|
|
117
|
+
|
|
118
|
+
except yaml.YAMLError as e:
|
|
119
|
+
self.logger.warning(
|
|
120
|
+
f"Invalid YAML frontmatter in {template_path.name}: {e}"
|
|
121
|
+
)
|
|
122
|
+
return None
|
|
123
|
+
except json.JSONDecodeError as e:
|
|
124
|
+
self.logger.warning(f"Invalid JSON in {template_path.name}: {e}")
|
|
125
|
+
return None
|
|
126
|
+
except Exception as e:
|
|
127
|
+
self.logger.warning(
|
|
128
|
+
f"Error reading template version from {template_path.name}: {e}"
|
|
129
|
+
)
|
|
130
|
+
return None
|
|
131
|
+
|
|
132
|
+
def _build_canonical_id_for_agent(self, agent_info: Dict[str, Any]) -> str:
|
|
133
|
+
"""Build or retrieve canonical_id for an agent.
|
|
134
|
+
|
|
135
|
+
NEW: Supports enhanced agent matching via canonical_id.
|
|
136
|
+
|
|
137
|
+
Priority:
|
|
138
|
+
1. Use existing canonical_id from agent_info if present
|
|
139
|
+
2. Generate from collection_id + agent_id if available
|
|
140
|
+
3. Fallback to legacy:{filename} for backward compatibility
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
agent_info: Agent dictionary with metadata
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
Canonical ID string for matching
|
|
147
|
+
|
|
148
|
+
Example:
|
|
149
|
+
Remote agent: "bobmatnyc/claude-mpm-agents:pm"
|
|
150
|
+
Legacy agent: "legacy:custom-agent"
|
|
151
|
+
"""
|
|
152
|
+
# Priority 1: Existing canonical_id
|
|
153
|
+
if "canonical_id" in agent_info:
|
|
154
|
+
return agent_info["canonical_id"]
|
|
155
|
+
|
|
156
|
+
# Priority 2: Generate from collection_id + agent_id
|
|
157
|
+
collection_id = agent_info.get("collection_id")
|
|
158
|
+
agent_id = agent_info.get("agent_id")
|
|
159
|
+
|
|
160
|
+
if collection_id and agent_id:
|
|
161
|
+
canonical_id = f"{collection_id}:{agent_id}"
|
|
162
|
+
# Cache it in agent_info for future use
|
|
163
|
+
agent_info["canonical_id"] = canonical_id
|
|
164
|
+
return canonical_id
|
|
165
|
+
|
|
166
|
+
# Priority 3: Fallback to legacy format
|
|
167
|
+
# Use filename or agent name
|
|
168
|
+
agent_name = agent_info.get("name") or agent_info.get("metadata", {}).get(
|
|
169
|
+
"name", "unknown"
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
# Extract filename from path
|
|
173
|
+
path_str = (
|
|
174
|
+
agent_info.get("path")
|
|
175
|
+
or agent_info.get("file_path")
|
|
176
|
+
or agent_info.get("source_file")
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
if path_str:
|
|
180
|
+
filename = Path(path_str).stem
|
|
181
|
+
canonical_id = f"legacy:{filename}"
|
|
182
|
+
else:
|
|
183
|
+
canonical_id = f"legacy:{agent_name}"
|
|
184
|
+
|
|
185
|
+
# Cache it
|
|
186
|
+
agent_info["canonical_id"] = canonical_id
|
|
187
|
+
return canonical_id
|
|
188
|
+
|
|
189
|
+
def discover_agents_from_all_sources(
|
|
190
|
+
self,
|
|
191
|
+
system_templates_dir: Optional[Path] = None,
|
|
192
|
+
project_agents_dir: Optional[Path] = None,
|
|
193
|
+
user_agents_dir: Optional[Path] = None,
|
|
194
|
+
agents_cache_dir: Optional[Path] = None,
|
|
195
|
+
working_directory: Optional[Path] = None,
|
|
196
|
+
) -> Dict[str, List[Dict[str, Any]]]:
|
|
197
|
+
"""Discover agents from all 4 tiers (system, user, cache, project).
|
|
198
|
+
|
|
199
|
+
Priority hierarchy (highest to lowest):
|
|
200
|
+
4. Project agents - Highest priority, project-specific customizations
|
|
201
|
+
3. Cached agents - GitHub-synced agents from cache
|
|
202
|
+
2. User agents - DEPRECATED, user-level customizations
|
|
203
|
+
1. System templates - Lowest priority, built-in agents
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
system_templates_dir: Directory containing system agent templates
|
|
207
|
+
project_agents_dir: Directory containing project-specific agents
|
|
208
|
+
user_agents_dir: Directory containing user custom agents (DEPRECATED)
|
|
209
|
+
agents_cache_dir: Directory containing cached agents from Git sources
|
|
210
|
+
working_directory: Current working directory for finding project agents
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
Dictionary mapping agent names to list of agent info from different sources
|
|
214
|
+
|
|
215
|
+
Deprecation Warning:
|
|
216
|
+
User-level agents are deprecated and will show a warning if found.
|
|
217
|
+
Use 'claude-mpm agents migrate-to-project' to migrate them.
|
|
218
|
+
"""
|
|
219
|
+
agents_by_name = {}
|
|
220
|
+
|
|
221
|
+
# Determine directories if not provided
|
|
222
|
+
if not system_templates_dir:
|
|
223
|
+
# Use default system templates location
|
|
224
|
+
from claude_mpm.config.paths import paths
|
|
225
|
+
|
|
226
|
+
system_templates_dir = paths.agents_dir / "templates"
|
|
227
|
+
|
|
228
|
+
if not project_agents_dir and working_directory:
|
|
229
|
+
# Check for project agents in working directory
|
|
230
|
+
project_agents_dir = working_directory / ".claude-mpm" / "agents"
|
|
231
|
+
if not project_agents_dir.exists():
|
|
232
|
+
project_agents_dir = None
|
|
233
|
+
|
|
234
|
+
if not user_agents_dir:
|
|
235
|
+
# Check for user agents in home directory
|
|
236
|
+
user_agents_dir = Path.home() / ".claude-mpm" / "agents"
|
|
237
|
+
if not user_agents_dir.exists():
|
|
238
|
+
user_agents_dir = None
|
|
239
|
+
|
|
240
|
+
if not agents_cache_dir:
|
|
241
|
+
# Check for agents in cache directory
|
|
242
|
+
cache_dir = Path.home() / ".claude-mpm" / "cache"
|
|
243
|
+
agents_cache_dir = cache_dir / "agents"
|
|
244
|
+
if not agents_cache_dir.exists():
|
|
245
|
+
agents_cache_dir = None
|
|
246
|
+
|
|
247
|
+
# Discover agents from each source in priority order
|
|
248
|
+
# Note: We process in reverse priority order (system first) and build up the dictionary
|
|
249
|
+
# The select_highest_version_agents() method will handle the actual prioritization
|
|
250
|
+
sources = [
|
|
251
|
+
("system", system_templates_dir),
|
|
252
|
+
("user", user_agents_dir),
|
|
253
|
+
("remote", agents_cache_dir),
|
|
254
|
+
("project", project_agents_dir),
|
|
255
|
+
]
|
|
256
|
+
|
|
257
|
+
# Track if we found user agents for deprecation warning
|
|
258
|
+
user_agents_found = False
|
|
259
|
+
|
|
260
|
+
for source_name, source_dir in sources:
|
|
261
|
+
if source_dir and source_dir.exists():
|
|
262
|
+
self.logger.debug(
|
|
263
|
+
f"Discovering agents from {source_name} source: {source_dir}"
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
# Use appropriate discovery service based on source type
|
|
267
|
+
if source_name == "remote":
|
|
268
|
+
# Remote agents are Markdown, use RemoteAgentDiscoveryService
|
|
269
|
+
remote_service = RemoteAgentDiscoveryService(source_dir)
|
|
270
|
+
agents = remote_service.discover_remote_agents()
|
|
271
|
+
else:
|
|
272
|
+
# Other sources are JSON, use AgentDiscoveryService
|
|
273
|
+
discovery_service = AgentDiscoveryService(source_dir)
|
|
274
|
+
# Pass log_discovery=False to avoid duplicate logging
|
|
275
|
+
agents = discovery_service.list_available_agents(
|
|
276
|
+
log_discovery=False
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
# Track user agents for deprecation warning
|
|
280
|
+
if source_name == "user" and agents:
|
|
281
|
+
user_agents_found = True
|
|
282
|
+
|
|
283
|
+
for agent_info in agents:
|
|
284
|
+
agent_name = agent_info.get("name") or agent_info.get(
|
|
285
|
+
"metadata", {}
|
|
286
|
+
).get("name")
|
|
287
|
+
if not agent_name:
|
|
288
|
+
continue
|
|
289
|
+
|
|
290
|
+
# Add source information
|
|
291
|
+
agent_info["source"] = source_name
|
|
292
|
+
agent_info["source_dir"] = str(source_dir)
|
|
293
|
+
|
|
294
|
+
# NEW: Build canonical_id for enhanced matching
|
|
295
|
+
canonical_id = self._build_canonical_id_for_agent(agent_info)
|
|
296
|
+
|
|
297
|
+
# Group by canonical_id (PRIMARY) for enhanced matching
|
|
298
|
+
# This allows matching agents from different sources with same canonical_id
|
|
299
|
+
# while maintaining backward compatibility with name-based matching
|
|
300
|
+
matching_key = canonical_id
|
|
301
|
+
|
|
302
|
+
# Initialize list if this is the first occurrence of this agent
|
|
303
|
+
if matching_key not in agents_by_name:
|
|
304
|
+
agents_by_name[matching_key] = []
|
|
305
|
+
|
|
306
|
+
agents_by_name[matching_key].append(agent_info)
|
|
307
|
+
|
|
308
|
+
# Use more specific log message
|
|
309
|
+
self.logger.info(
|
|
310
|
+
f"Discovered {len(agents)} {source_name} agent templates from {source_dir.name}"
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
# Show deprecation warning if user agents found
|
|
314
|
+
if user_agents_found:
|
|
315
|
+
self.logger.warning(
|
|
316
|
+
"\n"
|
|
317
|
+
"⚠️ DEPRECATION WARNING: User-level agents found in ~/.claude-mpm/agents/\n"
|
|
318
|
+
" User-level agent deployment is deprecated and will be removed in v5.0.0\n"
|
|
319
|
+
"\n"
|
|
320
|
+
" Why this change?\n"
|
|
321
|
+
" - Project isolation: Agents should be project-specific\n"
|
|
322
|
+
" - Version control: Project agents can be versioned with your code\n"
|
|
323
|
+
" - Team consistency: All team members use the same agents\n"
|
|
324
|
+
"\n"
|
|
325
|
+
" Migration:\n"
|
|
326
|
+
" 1. Run: claude-mpm agents migrate-to-project\n"
|
|
327
|
+
" 2. Verify agents work in .claude-mpm/agents/\n"
|
|
328
|
+
" 3. Remove: rm -rf ~/.claude-mpm/agents/\n"
|
|
329
|
+
"\n"
|
|
330
|
+
" Learn more: https://docs.claude-mpm.dev/agents/migration\n"
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
return agents_by_name
|
|
334
|
+
|
|
335
|
+
def get_agents_by_collection(
|
|
336
|
+
self,
|
|
337
|
+
collection_id: str,
|
|
338
|
+
agents_cache_dir: Optional[Path] = None,
|
|
339
|
+
) -> List[Dict[str, Any]]:
|
|
340
|
+
"""Get all agents from a specific collection.
|
|
341
|
+
|
|
342
|
+
NEW: Enables collection-based agent selection.
|
|
343
|
+
|
|
344
|
+
Args:
|
|
345
|
+
collection_id: Collection identifier (e.g., "bobmatnyc/claude-mpm-agents")
|
|
346
|
+
agents_cache_dir: Directory containing agents cache
|
|
347
|
+
|
|
348
|
+
Returns:
|
|
349
|
+
List of agent dictionaries from the specified collection
|
|
350
|
+
|
|
351
|
+
Example:
|
|
352
|
+
>>> service = MultiSourceAgentDeploymentService()
|
|
353
|
+
>>> agents = service.get_agents_by_collection("bobmatnyc/claude-mpm-agents")
|
|
354
|
+
>>> len(agents)
|
|
355
|
+
45
|
|
356
|
+
"""
|
|
357
|
+
if not agents_cache_dir:
|
|
358
|
+
cache_dir = Path.home() / ".claude-mpm" / "cache"
|
|
359
|
+
agents_cache_dir = cache_dir / "agents"
|
|
360
|
+
|
|
361
|
+
if not agents_cache_dir.exists():
|
|
362
|
+
self.logger.warning(f"Agents cache directory not found: {agents_cache_dir}")
|
|
363
|
+
return []
|
|
364
|
+
|
|
365
|
+
# Use RemoteAgentDiscoveryService to get collection agents
|
|
366
|
+
remote_service = RemoteAgentDiscoveryService(agents_cache_dir)
|
|
367
|
+
collection_agents = remote_service.get_agents_by_collection(collection_id)
|
|
368
|
+
|
|
369
|
+
self.logger.info(
|
|
370
|
+
f"Retrieved {len(collection_agents)} agents from collection '{collection_id}'"
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
return collection_agents
|
|
374
|
+
|
|
375
|
+
def select_highest_version_agents(
|
|
376
|
+
self, agents_by_name: Dict[str, List[Dict[str, Any]]]
|
|
377
|
+
) -> Dict[str, Dict[str, Any]]:
|
|
378
|
+
"""Select the highest version agent from multiple sources.
|
|
379
|
+
|
|
380
|
+
Args:
|
|
381
|
+
agents_by_name: Dictionary mapping agent names to list of agent info
|
|
382
|
+
|
|
383
|
+
Returns:
|
|
384
|
+
Dictionary mapping agent names to the highest version agent info
|
|
385
|
+
"""
|
|
386
|
+
selected_agents = {}
|
|
387
|
+
|
|
388
|
+
for agent_name, agent_versions in agents_by_name.items():
|
|
389
|
+
if not agent_versions:
|
|
390
|
+
continue
|
|
391
|
+
|
|
392
|
+
# If only one version exists, use it
|
|
393
|
+
if len(agent_versions) == 1:
|
|
394
|
+
selected_agents[agent_name] = agent_versions[0]
|
|
395
|
+
self.logger.debug(
|
|
396
|
+
f"Agent '{agent_name}' has single source: {agent_versions[0]['source']}"
|
|
397
|
+
)
|
|
398
|
+
continue
|
|
399
|
+
|
|
400
|
+
# Compare versions to find the highest
|
|
401
|
+
highest_version_agent = None
|
|
402
|
+
highest_version_tuple = (0, 0, 0)
|
|
403
|
+
|
|
404
|
+
for agent_info in agent_versions:
|
|
405
|
+
version_str = agent_info.get("version", "0.0.0")
|
|
406
|
+
version_tuple = self.version_manager.parse_version(version_str)
|
|
407
|
+
|
|
408
|
+
self.logger.debug(
|
|
409
|
+
f"Agent '{agent_name}' from {agent_info['source']}: "
|
|
410
|
+
f"version {version_str} -> {version_tuple}"
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
# Compare with current highest
|
|
414
|
+
if (
|
|
415
|
+
self.version_manager.compare_versions(
|
|
416
|
+
version_tuple, highest_version_tuple
|
|
417
|
+
)
|
|
418
|
+
> 0
|
|
419
|
+
):
|
|
420
|
+
highest_version_agent = agent_info
|
|
421
|
+
highest_version_tuple = version_tuple
|
|
422
|
+
|
|
423
|
+
if highest_version_agent:
|
|
424
|
+
selected_agents[agent_name] = highest_version_agent
|
|
425
|
+
self.logger.info(
|
|
426
|
+
f"Selected agent '{agent_name}' version {highest_version_agent['version']} "
|
|
427
|
+
f"from {highest_version_agent['source']} source"
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
# Log if a higher priority source was overridden by version
|
|
431
|
+
for other_agent in agent_versions:
|
|
432
|
+
if other_agent != highest_version_agent:
|
|
433
|
+
# Parse both versions for comparison
|
|
434
|
+
other_version = self.version_manager.parse_version(
|
|
435
|
+
other_agent.get("version", "0.0.0")
|
|
436
|
+
)
|
|
437
|
+
highest_version = self.version_manager.parse_version(
|
|
438
|
+
highest_version_agent.get("version", "0.0.0")
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
# Compare the versions
|
|
442
|
+
version_comparison = self.version_manager.compare_versions(
|
|
443
|
+
other_version, highest_version
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
# Only warn if the other version is actually lower
|
|
447
|
+
if version_comparison < 0:
|
|
448
|
+
if (
|
|
449
|
+
other_agent["source"] == "project"
|
|
450
|
+
and highest_version_agent["source"] == "system"
|
|
451
|
+
):
|
|
452
|
+
self.logger.warning(
|
|
453
|
+
f"Project agent '{agent_name}' v{other_agent['version']} "
|
|
454
|
+
f"overridden by higher system version v{highest_version_agent['version']}"
|
|
455
|
+
)
|
|
456
|
+
elif other_agent[
|
|
457
|
+
"source"
|
|
458
|
+
] == "user" and highest_version_agent["source"] in [
|
|
459
|
+
"system",
|
|
460
|
+
"project",
|
|
461
|
+
]:
|
|
462
|
+
self.logger.warning(
|
|
463
|
+
f"User agent '{agent_name}' v{other_agent['version']} "
|
|
464
|
+
f"overridden by higher {highest_version_agent['source']} version v{highest_version_agent['version']}"
|
|
465
|
+
)
|
|
466
|
+
elif (
|
|
467
|
+
version_comparison == 0
|
|
468
|
+
and other_agent["source"] != highest_version_agent["source"]
|
|
469
|
+
):
|
|
470
|
+
# Log info when versions are equal but different sources
|
|
471
|
+
self.logger.info(
|
|
472
|
+
f"Using {highest_version_agent['source']} source for '{agent_name}' "
|
|
473
|
+
f"(same version v{highest_version_agent['version']} as {other_agent['source']} source)"
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
return selected_agents
|
|
477
|
+
|
|
478
|
+
def get_agents_for_deployment(
|
|
479
|
+
self,
|
|
480
|
+
system_templates_dir: Optional[Path] = None,
|
|
481
|
+
project_agents_dir: Optional[Path] = None,
|
|
482
|
+
user_agents_dir: Optional[Path] = None,
|
|
483
|
+
agents_cache_dir: Optional[Path] = None,
|
|
484
|
+
working_directory: Optional[Path] = None,
|
|
485
|
+
excluded_agents: Optional[List[str]] = None,
|
|
486
|
+
config: Optional[Config] = None,
|
|
487
|
+
cleanup_outdated: bool = True,
|
|
488
|
+
) -> Tuple[Dict[str, Path], Dict[str, str], Dict[str, Any]]:
|
|
489
|
+
"""Get the highest version agents from all 4 tiers for deployment.
|
|
490
|
+
|
|
491
|
+
Args:
|
|
492
|
+
system_templates_dir: Directory containing system agent templates
|
|
493
|
+
project_agents_dir: Directory containing project-specific agents
|
|
494
|
+
user_agents_dir: Directory containing user custom agents (DEPRECATED)
|
|
495
|
+
agents_cache_dir: Directory containing cached agents from Git sources
|
|
496
|
+
working_directory: Current working directory for finding project agents
|
|
497
|
+
excluded_agents: List of agent names to exclude from deployment
|
|
498
|
+
config: Configuration object for additional filtering
|
|
499
|
+
cleanup_outdated: Whether to cleanup outdated user agents (default: True)
|
|
500
|
+
|
|
501
|
+
Returns:
|
|
502
|
+
Tuple of:
|
|
503
|
+
- Dictionary mapping agent names to template file paths
|
|
504
|
+
- Dictionary mapping agent names to their source
|
|
505
|
+
- Dictionary with cleanup results (removed, preserved, errors)
|
|
506
|
+
"""
|
|
507
|
+
# Discover all available agents from 4 tiers
|
|
508
|
+
agents_by_name = self.discover_agents_from_all_sources(
|
|
509
|
+
system_templates_dir=system_templates_dir,
|
|
510
|
+
project_agents_dir=project_agents_dir,
|
|
511
|
+
user_agents_dir=user_agents_dir,
|
|
512
|
+
agents_cache_dir=agents_cache_dir,
|
|
513
|
+
working_directory=working_directory,
|
|
514
|
+
)
|
|
515
|
+
|
|
516
|
+
# Select highest version for each agent
|
|
517
|
+
selected_agents = self.select_highest_version_agents(agents_by_name)
|
|
518
|
+
|
|
519
|
+
# Clean up outdated user agents if enabled
|
|
520
|
+
cleanup_results = {"removed": [], "preserved": [], "errors": []}
|
|
521
|
+
if cleanup_outdated:
|
|
522
|
+
# Check if cleanup is enabled in config or environment
|
|
523
|
+
cleanup_enabled = True
|
|
524
|
+
|
|
525
|
+
# Check environment variable first (for CI/CD and testing)
|
|
526
|
+
env_cleanup = os.environ.get("CLAUDE_MPM_CLEANUP_USER_AGENTS", "").lower()
|
|
527
|
+
if env_cleanup in ["false", "0", "no", "disabled"]:
|
|
528
|
+
cleanup_enabled = False
|
|
529
|
+
self.logger.debug(
|
|
530
|
+
"User agent cleanup disabled via environment variable"
|
|
531
|
+
)
|
|
532
|
+
|
|
533
|
+
# Check config if environment doesn't disable it
|
|
534
|
+
if cleanup_enabled and config:
|
|
535
|
+
cleanup_enabled = config.get(
|
|
536
|
+
"agent_deployment.cleanup_outdated_user_agents", True
|
|
537
|
+
)
|
|
538
|
+
|
|
539
|
+
if cleanup_enabled:
|
|
540
|
+
cleanup_results = self.cleanup_outdated_user_agents(
|
|
541
|
+
agents_by_name, selected_agents
|
|
542
|
+
)
|
|
543
|
+
|
|
544
|
+
# Apply exclusion filters
|
|
545
|
+
if excluded_agents:
|
|
546
|
+
# Find agents to remove by matching normalized names
|
|
547
|
+
# Normalization handles: "Dart Engineer", "dart_engineer", "dart-engineer"
|
|
548
|
+
agents_to_remove = []
|
|
549
|
+
excluded_set = {_normalize_agent_name(name) for name in excluded_agents}
|
|
550
|
+
|
|
551
|
+
for canonical_id, agent_info in list(selected_agents.items()):
|
|
552
|
+
# Check agent name field (normalized)
|
|
553
|
+
agent_name = _normalize_agent_name(agent_info.get("name", ""))
|
|
554
|
+
|
|
555
|
+
# Also check the agent_id portion of canonical_id (after the colon)
|
|
556
|
+
# Example: "bobmatnyc/claude-mpm-agents:pm" -> "pm"
|
|
557
|
+
raw_agent_id = (
|
|
558
|
+
canonical_id.split(":")[-1] if ":" in canonical_id else canonical_id
|
|
559
|
+
)
|
|
560
|
+
agent_id = _normalize_agent_name(raw_agent_id)
|
|
561
|
+
|
|
562
|
+
# Check file stem from path (most reliable match)
|
|
563
|
+
file_stem = ""
|
|
564
|
+
path_str = agent_info.get("path") or agent_info.get("file_path")
|
|
565
|
+
if path_str:
|
|
566
|
+
file_stem = _normalize_agent_name(Path(path_str).stem)
|
|
567
|
+
|
|
568
|
+
if (
|
|
569
|
+
agent_name in excluded_set
|
|
570
|
+
or agent_id in excluded_set
|
|
571
|
+
or file_stem in excluded_set
|
|
572
|
+
):
|
|
573
|
+
agents_to_remove.append(canonical_id)
|
|
574
|
+
self.logger.info(
|
|
575
|
+
f"Excluding agent '{agent_info.get('name', raw_agent_id)}' "
|
|
576
|
+
f"(canonical_id: {canonical_id}) from deployment"
|
|
577
|
+
)
|
|
578
|
+
|
|
579
|
+
# Remove matched agents
|
|
580
|
+
for canonical_id in agents_to_remove:
|
|
581
|
+
del selected_agents[canonical_id]
|
|
582
|
+
|
|
583
|
+
# Apply config-based filtering if provided
|
|
584
|
+
if config:
|
|
585
|
+
selected_agents = self._apply_config_filters(selected_agents, config)
|
|
586
|
+
|
|
587
|
+
# Create deployment mappings
|
|
588
|
+
agents_to_deploy = {}
|
|
589
|
+
agent_sources = {}
|
|
590
|
+
|
|
591
|
+
for agent_name, agent_info in selected_agents.items():
|
|
592
|
+
# Defensive: Try multiple path fields for backward compatibility (ticket 1M-480)
|
|
593
|
+
# Priority: 'path' -> 'file_path' -> 'source_file'
|
|
594
|
+
path_str = (
|
|
595
|
+
agent_info.get("path")
|
|
596
|
+
or agent_info.get("file_path")
|
|
597
|
+
or agent_info.get("source_file")
|
|
598
|
+
)
|
|
599
|
+
|
|
600
|
+
if not path_str:
|
|
601
|
+
self.logger.warning(
|
|
602
|
+
f"Agent '{agent_name}' missing path information (no 'path', 'file_path', or 'source_file' field)"
|
|
603
|
+
)
|
|
604
|
+
continue
|
|
605
|
+
|
|
606
|
+
template_path = Path(path_str)
|
|
607
|
+
if template_path.exists():
|
|
608
|
+
# Use the file stem as the key for consistency
|
|
609
|
+
file_stem = template_path.stem
|
|
610
|
+
agents_to_deploy[file_stem] = template_path
|
|
611
|
+
agent_sources[file_stem] = agent_info["source"]
|
|
612
|
+
|
|
613
|
+
# Also keep the display name mapping for logging
|
|
614
|
+
if file_stem != agent_name:
|
|
615
|
+
self.logger.debug(f"Mapping '{agent_name}' -> '{file_stem}'")
|
|
616
|
+
else:
|
|
617
|
+
self.logger.warning(
|
|
618
|
+
f"Template file not found for agent '{agent_name}': {template_path}"
|
|
619
|
+
)
|
|
620
|
+
|
|
621
|
+
self.logger.info(
|
|
622
|
+
f"Selected {len(agents_to_deploy)} agents for deployment "
|
|
623
|
+
f"(system: {sum(1 for s in agent_sources.values() if s == 'system')}, "
|
|
624
|
+
f"project: {sum(1 for s in agent_sources.values() if s == 'project')}, "
|
|
625
|
+
f"user: {sum(1 for s in agent_sources.values() if s == 'user')})"
|
|
626
|
+
)
|
|
627
|
+
|
|
628
|
+
return agents_to_deploy, agent_sources, cleanup_results
|
|
629
|
+
|
|
630
|
+
def cleanup_excluded_agents(
|
|
631
|
+
self,
|
|
632
|
+
deployed_agents_dir: Path,
|
|
633
|
+
agents_to_deploy: Dict[str, Path],
|
|
634
|
+
) -> Dict[str, Any]:
|
|
635
|
+
"""Remove agents from deployed directory that aren't in the deployment list.
|
|
636
|
+
|
|
637
|
+
Similar to skill cleanup logic, this removes agents that were previously
|
|
638
|
+
deployed but are no longer in the enabled agents list (e.g., filtered out
|
|
639
|
+
by profile configuration).
|
|
640
|
+
|
|
641
|
+
Args:
|
|
642
|
+
deployed_agents_dir: Directory containing deployed agents (~/.claude/agents)
|
|
643
|
+
agents_to_deploy: Dictionary mapping agent file stems to template paths
|
|
644
|
+
|
|
645
|
+
Returns:
|
|
646
|
+
Dictionary with cleanup results:
|
|
647
|
+
- removed: List of removed agent names
|
|
648
|
+
- errors: List of errors during cleanup
|
|
649
|
+
"""
|
|
650
|
+
cleanup_results = {"removed": [], "errors": []}
|
|
651
|
+
|
|
652
|
+
# Safety check - only operate on deployed agents directory
|
|
653
|
+
if not deployed_agents_dir.exists():
|
|
654
|
+
self.logger.debug(
|
|
655
|
+
"Deployed agents directory does not exist, no cleanup needed"
|
|
656
|
+
)
|
|
657
|
+
return cleanup_results
|
|
658
|
+
|
|
659
|
+
# Build set of agent names that should exist (file stems without .md extension)
|
|
660
|
+
expected_agents = set(agents_to_deploy.keys())
|
|
661
|
+
|
|
662
|
+
try:
|
|
663
|
+
# Check each file in deployed_agents_dir
|
|
664
|
+
for item in deployed_agents_dir.iterdir():
|
|
665
|
+
# Only process .md files
|
|
666
|
+
if not item.is_file() or item.suffix != ".md":
|
|
667
|
+
continue
|
|
668
|
+
|
|
669
|
+
# Skip hidden files
|
|
670
|
+
if item.name.startswith("."):
|
|
671
|
+
continue
|
|
672
|
+
|
|
673
|
+
# Get agent name (file stem)
|
|
674
|
+
agent_name = item.stem
|
|
675
|
+
|
|
676
|
+
# Check if this agent should be kept
|
|
677
|
+
if agent_name not in expected_agents:
|
|
678
|
+
try:
|
|
679
|
+
# Security: Validate path is within deployed_agents_dir
|
|
680
|
+
resolved_item = item.resolve()
|
|
681
|
+
resolved_target = deployed_agents_dir.resolve()
|
|
682
|
+
|
|
683
|
+
if not str(resolved_item).startswith(str(resolved_target)):
|
|
684
|
+
self.logger.error(
|
|
685
|
+
f"Refusing to remove path outside target directory: {item}"
|
|
686
|
+
)
|
|
687
|
+
cleanup_results["errors"].append(
|
|
688
|
+
{
|
|
689
|
+
"agent": agent_name,
|
|
690
|
+
"error": "Path outside target directory",
|
|
691
|
+
}
|
|
692
|
+
)
|
|
693
|
+
continue
|
|
694
|
+
|
|
695
|
+
# Remove the agent file
|
|
696
|
+
item.unlink()
|
|
697
|
+
cleanup_results["removed"].append(agent_name)
|
|
698
|
+
self.logger.info(f"Removed excluded agent: {agent_name}")
|
|
699
|
+
|
|
700
|
+
except PermissionError as e:
|
|
701
|
+
error_msg = f"Permission denied removing {agent_name}: {e}"
|
|
702
|
+
self.logger.error(error_msg)
|
|
703
|
+
cleanup_results["errors"].append(
|
|
704
|
+
{"agent": agent_name, "error": error_msg}
|
|
705
|
+
)
|
|
706
|
+
except Exception as e:
|
|
707
|
+
error_msg = f"Error removing {agent_name}: {e}"
|
|
708
|
+
self.logger.error(error_msg)
|
|
709
|
+
cleanup_results["errors"].append(
|
|
710
|
+
{"agent": agent_name, "error": error_msg}
|
|
711
|
+
)
|
|
712
|
+
|
|
713
|
+
except Exception as e:
|
|
714
|
+
self.logger.error(f"Error during agent cleanup: {e}")
|
|
715
|
+
cleanup_results["errors"].append(
|
|
716
|
+
{"agent": "cleanup_process", "error": str(e)}
|
|
717
|
+
)
|
|
718
|
+
|
|
719
|
+
# Log cleanup summary
|
|
720
|
+
if cleanup_results["removed"]:
|
|
721
|
+
self.logger.info(
|
|
722
|
+
f"Cleanup complete: removed {len(cleanup_results['removed'])} excluded agents"
|
|
723
|
+
)
|
|
724
|
+
if cleanup_results["errors"]:
|
|
725
|
+
self.logger.warning(
|
|
726
|
+
f"Encountered {len(cleanup_results['errors'])} errors during cleanup"
|
|
727
|
+
)
|
|
728
|
+
|
|
729
|
+
return cleanup_results
|
|
730
|
+
|
|
731
|
+
def cleanup_outdated_user_agents(
|
|
732
|
+
self,
|
|
733
|
+
agents_by_name: Dict[str, List[Dict[str, Any]]],
|
|
734
|
+
selected_agents: Dict[str, Dict[str, Any]],
|
|
735
|
+
) -> Dict[str, Any]:
|
|
736
|
+
"""Remove outdated user agents when project or system agents have higher versions.
|
|
737
|
+
|
|
738
|
+
WHY: When project agents are updated to newer versions, outdated user agent
|
|
739
|
+
copies should be removed to prevent confusion and ensure the latest version
|
|
740
|
+
is always used. User agents with same or higher versions are preserved to
|
|
741
|
+
respect user customizations.
|
|
742
|
+
|
|
743
|
+
Args:
|
|
744
|
+
agents_by_name: Dictionary mapping agent names to list of agent info from different sources
|
|
745
|
+
selected_agents: Dictionary mapping agent names to the selected highest version agent
|
|
746
|
+
|
|
747
|
+
Returns:
|
|
748
|
+
Dictionary with cleanup results:
|
|
749
|
+
- removed: List of removed agent info
|
|
750
|
+
- preserved: List of preserved agent info with reasons
|
|
751
|
+
- errors: List of errors during cleanup
|
|
752
|
+
"""
|
|
753
|
+
cleanup_results = {"removed": [], "preserved": [], "errors": []}
|
|
754
|
+
|
|
755
|
+
# Get user agents directory
|
|
756
|
+
user_agents_dir = Path.home() / ".claude-mpm" / "agents"
|
|
757
|
+
|
|
758
|
+
# Safety check - only operate on user agents directory
|
|
759
|
+
if not user_agents_dir.exists():
|
|
760
|
+
self.logger.debug("User agents directory does not exist, no cleanup needed")
|
|
761
|
+
return cleanup_results
|
|
762
|
+
|
|
763
|
+
for agent_name, agent_versions in agents_by_name.items():
|
|
764
|
+
# Skip if only one version exists
|
|
765
|
+
if len(agent_versions) < 2:
|
|
766
|
+
continue
|
|
767
|
+
|
|
768
|
+
selected = selected_agents.get(agent_name)
|
|
769
|
+
if not selected:
|
|
770
|
+
continue
|
|
771
|
+
|
|
772
|
+
# Process each version of this agent
|
|
773
|
+
for agent_info in agent_versions:
|
|
774
|
+
# Only consider user agents for cleanup
|
|
775
|
+
if agent_info["source"] != "user":
|
|
776
|
+
continue
|
|
777
|
+
|
|
778
|
+
# Defensive: Get path from agent_info (ticket 1M-480)
|
|
779
|
+
path_str = (
|
|
780
|
+
agent_info.get("path")
|
|
781
|
+
or agent_info.get("file_path")
|
|
782
|
+
or agent_info.get("source_file")
|
|
783
|
+
)
|
|
784
|
+
if not path_str:
|
|
785
|
+
self.logger.warning(
|
|
786
|
+
f"User agent '{agent_name}' missing path information, skipping cleanup"
|
|
787
|
+
)
|
|
788
|
+
continue
|
|
789
|
+
|
|
790
|
+
# Safety check - ensure path is within user agents directory
|
|
791
|
+
user_agent_path = Path(path_str)
|
|
792
|
+
try:
|
|
793
|
+
# Resolve paths to compare them safely
|
|
794
|
+
resolved_user_path = user_agent_path.resolve()
|
|
795
|
+
resolved_user_agents_dir = user_agents_dir.resolve()
|
|
796
|
+
|
|
797
|
+
# Verify the agent is actually in the user agents directory
|
|
798
|
+
if not str(resolved_user_path).startswith(
|
|
799
|
+
str(resolved_user_agents_dir)
|
|
800
|
+
):
|
|
801
|
+
self.logger.warning(
|
|
802
|
+
f"Skipping cleanup for {agent_name}: path {user_agent_path} "
|
|
803
|
+
f"is not within user agents directory"
|
|
804
|
+
)
|
|
805
|
+
cleanup_results["errors"].append(
|
|
806
|
+
{
|
|
807
|
+
"agent": agent_name,
|
|
808
|
+
"error": "Path outside user agents directory",
|
|
809
|
+
}
|
|
810
|
+
)
|
|
811
|
+
continue
|
|
812
|
+
except Exception as e:
|
|
813
|
+
self.logger.error(f"Error resolving paths for {agent_name}: {e}")
|
|
814
|
+
cleanup_results["errors"].append(
|
|
815
|
+
{"agent": agent_name, "error": f"Path resolution error: {e}"}
|
|
816
|
+
)
|
|
817
|
+
continue
|
|
818
|
+
|
|
819
|
+
# Compare versions
|
|
820
|
+
user_version = self.version_manager.parse_version(
|
|
821
|
+
agent_info.get("version", "0.0.0")
|
|
822
|
+
)
|
|
823
|
+
selected_version = self.version_manager.parse_version(
|
|
824
|
+
selected.get("version", "0.0.0")
|
|
825
|
+
)
|
|
826
|
+
|
|
827
|
+
version_comparison = self.version_manager.compare_versions(
|
|
828
|
+
user_version, selected_version
|
|
829
|
+
)
|
|
830
|
+
|
|
831
|
+
# Determine action based on version comparison and selected source
|
|
832
|
+
if version_comparison < 0 and selected["source"] in [
|
|
833
|
+
"project",
|
|
834
|
+
"system",
|
|
835
|
+
]:
|
|
836
|
+
# User agent has lower version than selected project/system agent - remove it
|
|
837
|
+
if user_agent_path.exists():
|
|
838
|
+
try:
|
|
839
|
+
# Log before removal for audit trail
|
|
840
|
+
self.logger.info(
|
|
841
|
+
f"Removing outdated user agent: {agent_name} "
|
|
842
|
+
f"v{self.version_manager.format_version_display(user_version)} "
|
|
843
|
+
f"(superseded by {selected['source']} "
|
|
844
|
+
f"v{self.version_manager.format_version_display(selected_version)})"
|
|
845
|
+
)
|
|
846
|
+
|
|
847
|
+
# Remove the file
|
|
848
|
+
user_agent_path.unlink()
|
|
849
|
+
|
|
850
|
+
cleanup_results["removed"].append(
|
|
851
|
+
{
|
|
852
|
+
"name": agent_name,
|
|
853
|
+
"version": self.version_manager.format_version_display(
|
|
854
|
+
user_version
|
|
855
|
+
),
|
|
856
|
+
"path": str(user_agent_path),
|
|
857
|
+
"reason": f"Superseded by {selected['source']} v{self.version_manager.format_version_display(selected_version)}",
|
|
858
|
+
}
|
|
859
|
+
)
|
|
860
|
+
except PermissionError as e:
|
|
861
|
+
error_msg = f"Permission denied removing {agent_name}: {e}"
|
|
862
|
+
self.logger.error(error_msg)
|
|
863
|
+
cleanup_results["errors"].append(
|
|
864
|
+
{"agent": agent_name, "error": error_msg}
|
|
865
|
+
)
|
|
866
|
+
except Exception as e:
|
|
867
|
+
error_msg = f"Error removing {agent_name}: {e}"
|
|
868
|
+
self.logger.error(error_msg)
|
|
869
|
+
cleanup_results["errors"].append(
|
|
870
|
+
{"agent": agent_name, "error": error_msg}
|
|
871
|
+
)
|
|
872
|
+
else:
|
|
873
|
+
# Preserve the user agent
|
|
874
|
+
if version_comparison >= 0:
|
|
875
|
+
reason = "User version same or higher than selected version"
|
|
876
|
+
elif selected["source"] == "user":
|
|
877
|
+
reason = "User agent is the selected version"
|
|
878
|
+
else:
|
|
879
|
+
reason = "User customization preserved"
|
|
880
|
+
|
|
881
|
+
cleanup_results["preserved"].append(
|
|
882
|
+
{
|
|
883
|
+
"name": agent_name,
|
|
884
|
+
"version": self.version_manager.format_version_display(
|
|
885
|
+
user_version
|
|
886
|
+
),
|
|
887
|
+
"reason": reason,
|
|
888
|
+
}
|
|
889
|
+
)
|
|
890
|
+
|
|
891
|
+
self.logger.debug(
|
|
892
|
+
f"Preserving user agent {agent_name} "
|
|
893
|
+
f"v{self.version_manager.format_version_display(user_version)}: {reason}"
|
|
894
|
+
)
|
|
895
|
+
|
|
896
|
+
# Log cleanup summary
|
|
897
|
+
if cleanup_results["removed"]:
|
|
898
|
+
self.logger.info(
|
|
899
|
+
f"Cleanup complete: removed {len(cleanup_results['removed'])} outdated user agents"
|
|
900
|
+
)
|
|
901
|
+
if cleanup_results["preserved"]:
|
|
902
|
+
self.logger.debug(
|
|
903
|
+
f"Preserved {len(cleanup_results['preserved'])} user agents"
|
|
904
|
+
)
|
|
905
|
+
if cleanup_results["errors"]:
|
|
906
|
+
self.logger.warning(
|
|
907
|
+
f"Encountered {len(cleanup_results['errors'])} errors during cleanup"
|
|
908
|
+
)
|
|
909
|
+
|
|
910
|
+
return cleanup_results
|
|
911
|
+
|
|
912
|
+
def _is_user_created_agent(self, agent_file: Path) -> bool:
|
|
913
|
+
"""Check if an agent is user-created based on metadata.
|
|
914
|
+
|
|
915
|
+
User agents are identified by:
|
|
916
|
+
- Lack of MPM authorship indicators
|
|
917
|
+
- Missing version or v0.0.0
|
|
918
|
+
- Certain naming patterns
|
|
919
|
+
|
|
920
|
+
Args:
|
|
921
|
+
agent_file: Path to the agent file to check
|
|
922
|
+
|
|
923
|
+
Returns:
|
|
924
|
+
True if the agent appears to be user-created, False otherwise
|
|
925
|
+
"""
|
|
926
|
+
try:
|
|
927
|
+
content = agent_file.read_text()
|
|
928
|
+
|
|
929
|
+
# Check for MPM authorship indicators
|
|
930
|
+
mpm_indicators = [
|
|
931
|
+
"author: claude-mpm",
|
|
932
|
+
"author: 'claude-mpm'",
|
|
933
|
+
'author: "claude-mpm"',
|
|
934
|
+
"author: Claude MPM",
|
|
935
|
+
"Claude MPM Team",
|
|
936
|
+
"Generated by Claude MPM",
|
|
937
|
+
"claude-mpm-project",
|
|
938
|
+
]
|
|
939
|
+
|
|
940
|
+
for indicator in mpm_indicators:
|
|
941
|
+
if indicator.lower() in content.lower():
|
|
942
|
+
return False # This is an MPM agent
|
|
943
|
+
|
|
944
|
+
# Check for version 0.0.0 (typical user agent default)
|
|
945
|
+
if "version: 0.0.0" in content or "version: '0.0.0'" in content:
|
|
946
|
+
return True
|
|
947
|
+
|
|
948
|
+
return True # Default to user-created if no MPM indicators
|
|
949
|
+
|
|
950
|
+
except Exception:
|
|
951
|
+
return True # Default to user-created if we can't read it
|
|
952
|
+
|
|
953
|
+
def _apply_config_filters(
|
|
954
|
+
self, selected_agents: Dict[str, Dict[str, Any]], config: Config
|
|
955
|
+
) -> Dict[str, Dict[str, Any]]:
|
|
956
|
+
"""Apply configuration-based filtering to selected agents.
|
|
957
|
+
|
|
958
|
+
Args:
|
|
959
|
+
selected_agents: Dictionary of selected agents
|
|
960
|
+
config: Configuration object
|
|
961
|
+
|
|
962
|
+
Returns:
|
|
963
|
+
Filtered dictionary of agents
|
|
964
|
+
"""
|
|
965
|
+
filtered_agents = {}
|
|
966
|
+
|
|
967
|
+
# Get exclusion patterns from config
|
|
968
|
+
exclusion_patterns = config.get("agent_deployment.exclusion_patterns", [])
|
|
969
|
+
|
|
970
|
+
# Get environment-specific exclusions
|
|
971
|
+
environment = config.get("environment", "development")
|
|
972
|
+
env_exclusions = config.get(f"agent_deployment.{environment}_exclusions", [])
|
|
973
|
+
|
|
974
|
+
for agent_name, agent_info in selected_agents.items():
|
|
975
|
+
# Check exclusion patterns
|
|
976
|
+
excluded = False
|
|
977
|
+
|
|
978
|
+
for pattern in exclusion_patterns:
|
|
979
|
+
if pattern in agent_name:
|
|
980
|
+
self.logger.debug(
|
|
981
|
+
f"Excluding '{agent_name}' due to pattern '{pattern}'"
|
|
982
|
+
)
|
|
983
|
+
excluded = True
|
|
984
|
+
break
|
|
985
|
+
|
|
986
|
+
# Check environment exclusions
|
|
987
|
+
if not excluded and agent_name in env_exclusions:
|
|
988
|
+
self.logger.debug(
|
|
989
|
+
f"Excluding '{agent_name}' due to {environment} environment"
|
|
990
|
+
)
|
|
991
|
+
excluded = True
|
|
992
|
+
|
|
993
|
+
if not excluded:
|
|
994
|
+
filtered_agents[agent_name] = agent_info
|
|
995
|
+
|
|
996
|
+
return filtered_agents
|
|
997
|
+
|
|
998
|
+
def compare_deployed_versions(
|
|
999
|
+
self,
|
|
1000
|
+
deployed_agents_dir: Path,
|
|
1001
|
+
agents_to_deploy: Dict[str, Path],
|
|
1002
|
+
agent_sources: Dict[str, str],
|
|
1003
|
+
) -> Dict[str, Any]:
|
|
1004
|
+
"""Compare deployed agent versions with candidates for deployment.
|
|
1005
|
+
|
|
1006
|
+
Args:
|
|
1007
|
+
deployed_agents_dir: Directory containing currently deployed agents
|
|
1008
|
+
agents_to_deploy: Dictionary mapping agent names to template paths
|
|
1009
|
+
agent_sources: Dictionary mapping agent names to their sources
|
|
1010
|
+
|
|
1011
|
+
Returns:
|
|
1012
|
+
Dictionary with comparison results including which agents need updates
|
|
1013
|
+
"""
|
|
1014
|
+
comparison_results = {
|
|
1015
|
+
"needs_update": [],
|
|
1016
|
+
"up_to_date": [],
|
|
1017
|
+
"new_agents": [],
|
|
1018
|
+
"orphaned_agents": [], # System agents without templates
|
|
1019
|
+
"user_agents": [], # User-created agents (no templates required)
|
|
1020
|
+
"version_upgrades": [],
|
|
1021
|
+
"version_downgrades": [],
|
|
1022
|
+
"source_changes": [],
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
for agent_name, template_path in agents_to_deploy.items():
|
|
1026
|
+
deployed_file = deployed_agents_dir / f"{agent_name}.md"
|
|
1027
|
+
|
|
1028
|
+
if not deployed_file.exists():
|
|
1029
|
+
comparison_results["new_agents"].append(
|
|
1030
|
+
{
|
|
1031
|
+
"name": agent_name,
|
|
1032
|
+
"source": agent_sources[agent_name],
|
|
1033
|
+
"template": str(template_path),
|
|
1034
|
+
}
|
|
1035
|
+
)
|
|
1036
|
+
comparison_results["needs_update"].append(agent_name)
|
|
1037
|
+
continue
|
|
1038
|
+
|
|
1039
|
+
# Read template version using format-aware helper
|
|
1040
|
+
version_string = self._read_template_version(template_path)
|
|
1041
|
+
if not version_string:
|
|
1042
|
+
self.logger.warning(
|
|
1043
|
+
f"Could not extract version from template for '{agent_name}', skipping"
|
|
1044
|
+
)
|
|
1045
|
+
continue
|
|
1046
|
+
|
|
1047
|
+
try:
|
|
1048
|
+
template_version = self.version_manager.parse_version(version_string)
|
|
1049
|
+
except Exception as e:
|
|
1050
|
+
self.logger.warning(
|
|
1051
|
+
f"Error parsing version '{version_string}' for '{agent_name}': {e}"
|
|
1052
|
+
)
|
|
1053
|
+
continue
|
|
1054
|
+
|
|
1055
|
+
# Read deployed version
|
|
1056
|
+
try:
|
|
1057
|
+
deployed_content = deployed_file.read_text()
|
|
1058
|
+
deployed_version, _, _ = (
|
|
1059
|
+
self.version_manager.extract_version_from_frontmatter(
|
|
1060
|
+
deployed_content
|
|
1061
|
+
)
|
|
1062
|
+
)
|
|
1063
|
+
|
|
1064
|
+
# Extract source from deployed agent if available
|
|
1065
|
+
deployed_source = "unknown"
|
|
1066
|
+
if "source:" in deployed_content:
|
|
1067
|
+
import re
|
|
1068
|
+
|
|
1069
|
+
source_match = re.search(
|
|
1070
|
+
r"^source:\s*(.+)$", deployed_content, re.MULTILINE
|
|
1071
|
+
)
|
|
1072
|
+
if source_match:
|
|
1073
|
+
deployed_source = source_match.group(1).strip()
|
|
1074
|
+
|
|
1075
|
+
# If source is still unknown, try to infer it from deployment context
|
|
1076
|
+
if deployed_source == "unknown":
|
|
1077
|
+
deployed_source = self._infer_agent_source_from_context(
|
|
1078
|
+
agent_name, deployed_agents_dir
|
|
1079
|
+
)
|
|
1080
|
+
except Exception as e:
|
|
1081
|
+
self.logger.warning(f"Error reading deployed agent '{agent_name}': {e}")
|
|
1082
|
+
comparison_results["needs_update"].append(agent_name)
|
|
1083
|
+
continue
|
|
1084
|
+
|
|
1085
|
+
# Compare versions
|
|
1086
|
+
version_comparison = self.version_manager.compare_versions(
|
|
1087
|
+
template_version, deployed_version
|
|
1088
|
+
)
|
|
1089
|
+
|
|
1090
|
+
if version_comparison > 0:
|
|
1091
|
+
# Template version is higher
|
|
1092
|
+
comparison_results["version_upgrades"].append(
|
|
1093
|
+
{
|
|
1094
|
+
"name": agent_name,
|
|
1095
|
+
"deployed_version": self.version_manager.format_version_display(
|
|
1096
|
+
deployed_version
|
|
1097
|
+
),
|
|
1098
|
+
"new_version": self.version_manager.format_version_display(
|
|
1099
|
+
template_version
|
|
1100
|
+
),
|
|
1101
|
+
"source": agent_sources[agent_name],
|
|
1102
|
+
"previous_source": deployed_source,
|
|
1103
|
+
}
|
|
1104
|
+
)
|
|
1105
|
+
comparison_results["needs_update"].append(agent_name)
|
|
1106
|
+
|
|
1107
|
+
if deployed_source != agent_sources[agent_name]:
|
|
1108
|
+
comparison_results["source_changes"].append(
|
|
1109
|
+
{
|
|
1110
|
+
"name": agent_name,
|
|
1111
|
+
"from_source": deployed_source,
|
|
1112
|
+
"to_source": agent_sources[agent_name],
|
|
1113
|
+
}
|
|
1114
|
+
)
|
|
1115
|
+
elif version_comparison < 0:
|
|
1116
|
+
# Deployed version is higher (shouldn't happen with proper version management)
|
|
1117
|
+
comparison_results["version_downgrades"].append(
|
|
1118
|
+
{
|
|
1119
|
+
"name": agent_name,
|
|
1120
|
+
"deployed_version": self.version_manager.format_version_display(
|
|
1121
|
+
deployed_version
|
|
1122
|
+
),
|
|
1123
|
+
"template_version": self.version_manager.format_version_display(
|
|
1124
|
+
template_version
|
|
1125
|
+
),
|
|
1126
|
+
"warning": "Deployed version is higher than template",
|
|
1127
|
+
}
|
|
1128
|
+
)
|
|
1129
|
+
# Don't add to needs_update - keep the higher version
|
|
1130
|
+
else:
|
|
1131
|
+
# Versions are equal
|
|
1132
|
+
comparison_results["up_to_date"].append(
|
|
1133
|
+
{
|
|
1134
|
+
"name": agent_name,
|
|
1135
|
+
"version": self.version_manager.format_version_display(
|
|
1136
|
+
deployed_version
|
|
1137
|
+
),
|
|
1138
|
+
"source": agent_sources[agent_name],
|
|
1139
|
+
}
|
|
1140
|
+
)
|
|
1141
|
+
|
|
1142
|
+
# Check for orphaned agents (deployed but no template)
|
|
1143
|
+
system_orphaned, user_orphaned = self._detect_orphaned_agents_simple(
|
|
1144
|
+
deployed_agents_dir, agents_to_deploy
|
|
1145
|
+
)
|
|
1146
|
+
comparison_results["orphaned_agents"] = system_orphaned
|
|
1147
|
+
comparison_results["user_agents"] = user_orphaned
|
|
1148
|
+
|
|
1149
|
+
# Log summary
|
|
1150
|
+
summary_parts = [
|
|
1151
|
+
f"{len(comparison_results['needs_update'])} need updates",
|
|
1152
|
+
f"{len(comparison_results['up_to_date'])} up to date",
|
|
1153
|
+
f"{len(comparison_results['new_agents'])} new agents",
|
|
1154
|
+
]
|
|
1155
|
+
if comparison_results["orphaned_agents"]:
|
|
1156
|
+
summary_parts.append(
|
|
1157
|
+
f"{len(comparison_results['orphaned_agents'])} system orphaned"
|
|
1158
|
+
)
|
|
1159
|
+
if comparison_results["user_agents"]:
|
|
1160
|
+
summary_parts.append(
|
|
1161
|
+
f"{len(comparison_results['user_agents'])} user agents"
|
|
1162
|
+
)
|
|
1163
|
+
|
|
1164
|
+
self.logger.info(f"Version comparison complete: {', '.join(summary_parts)}")
|
|
1165
|
+
|
|
1166
|
+
# Don't log upgrades here - let the caller decide when to log
|
|
1167
|
+
# This prevents repeated upgrade messages on every startup
|
|
1168
|
+
if comparison_results["version_upgrades"]:
|
|
1169
|
+
for upgrade in comparison_results["version_upgrades"]:
|
|
1170
|
+
self.logger.debug(
|
|
1171
|
+
f" Upgrade available: {upgrade['name']} "
|
|
1172
|
+
f"{upgrade['deployed_version']} -> {upgrade['new_version']} "
|
|
1173
|
+
f"(from {upgrade['source']})"
|
|
1174
|
+
)
|
|
1175
|
+
|
|
1176
|
+
if comparison_results["source_changes"]:
|
|
1177
|
+
for change in comparison_results["source_changes"]:
|
|
1178
|
+
self.logger.debug(
|
|
1179
|
+
f" Source change available: {change['name']} "
|
|
1180
|
+
f"from {change['from_source']} to {change['to_source']}"
|
|
1181
|
+
)
|
|
1182
|
+
|
|
1183
|
+
if comparison_results["version_downgrades"]:
|
|
1184
|
+
for downgrade in comparison_results["version_downgrades"]:
|
|
1185
|
+
# Changed from warning to debug - deployed versions higher than templates
|
|
1186
|
+
# are not errors, just informational
|
|
1187
|
+
self.logger.debug(
|
|
1188
|
+
f" Note: {downgrade['name']} deployed version "
|
|
1189
|
+
f"{downgrade['deployed_version']} is higher than template "
|
|
1190
|
+
f"{downgrade['template_version']} (keeping deployed version)"
|
|
1191
|
+
)
|
|
1192
|
+
|
|
1193
|
+
# Log system orphaned agents if found
|
|
1194
|
+
if comparison_results["orphaned_agents"]:
|
|
1195
|
+
self.logger.info(
|
|
1196
|
+
f"Found {len(comparison_results['orphaned_agents'])} system orphaned agent(s) "
|
|
1197
|
+
f"(deployed without templates):"
|
|
1198
|
+
)
|
|
1199
|
+
for orphan in comparison_results["orphaned_agents"]:
|
|
1200
|
+
self.logger.info(
|
|
1201
|
+
f" - {orphan['name']} v{orphan['version']} "
|
|
1202
|
+
f"(consider removing or creating a template)"
|
|
1203
|
+
)
|
|
1204
|
+
|
|
1205
|
+
# Log user agents at debug level if found
|
|
1206
|
+
if comparison_results["user_agents"]:
|
|
1207
|
+
self.logger.debug(
|
|
1208
|
+
f"Found {len(comparison_results['user_agents'])} user-created agent(s) "
|
|
1209
|
+
f"(no templates required):"
|
|
1210
|
+
)
|
|
1211
|
+
for user_agent in comparison_results["user_agents"]:
|
|
1212
|
+
self.logger.debug(
|
|
1213
|
+
f" - {user_agent['name']} v{user_agent['version']} "
|
|
1214
|
+
f"(user-created agent)"
|
|
1215
|
+
)
|
|
1216
|
+
|
|
1217
|
+
return comparison_results
|
|
1218
|
+
|
|
1219
|
+
def _infer_agent_source_from_context(
|
|
1220
|
+
self, agent_name: str, deployed_agents_dir: Path
|
|
1221
|
+
) -> str:
|
|
1222
|
+
"""Infer the source of a deployed agent when source metadata is missing.
|
|
1223
|
+
|
|
1224
|
+
This method attempts to determine the agent source based on:
|
|
1225
|
+
1. Deployment context (development vs pipx)
|
|
1226
|
+
2. Agent naming patterns
|
|
1227
|
+
3. Known system agents
|
|
1228
|
+
|
|
1229
|
+
Args:
|
|
1230
|
+
agent_name: Name of the agent
|
|
1231
|
+
deployed_agents_dir: Directory where agent is deployed
|
|
1232
|
+
|
|
1233
|
+
Returns:
|
|
1234
|
+
Inferred source string (system/project/user)
|
|
1235
|
+
"""
|
|
1236
|
+
# List of known system agents that ship with claude-mpm
|
|
1237
|
+
system_agents = {
|
|
1238
|
+
"pm",
|
|
1239
|
+
"engineer",
|
|
1240
|
+
"qa",
|
|
1241
|
+
"research",
|
|
1242
|
+
"documentation",
|
|
1243
|
+
"ops",
|
|
1244
|
+
"security",
|
|
1245
|
+
"web-ui",
|
|
1246
|
+
"api-qa",
|
|
1247
|
+
"version-control",
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
# If this is a known system agent, it's from system
|
|
1251
|
+
if agent_name in system_agents:
|
|
1252
|
+
return "system"
|
|
1253
|
+
|
|
1254
|
+
# Check deployment context
|
|
1255
|
+
from ....core.unified_paths import get_path_manager
|
|
1256
|
+
|
|
1257
|
+
path_manager = get_path_manager()
|
|
1258
|
+
|
|
1259
|
+
# If deployed_agents_dir is under user home/.claude/agents, check context
|
|
1260
|
+
user_claude_dir = Path.home() / ".claude" / "agents"
|
|
1261
|
+
if deployed_agents_dir == user_claude_dir:
|
|
1262
|
+
# Check if we're in development mode
|
|
1263
|
+
try:
|
|
1264
|
+
from ....core.unified_paths import DeploymentContext, PathContext
|
|
1265
|
+
|
|
1266
|
+
deployment_context = PathContext.detect_deployment_context()
|
|
1267
|
+
|
|
1268
|
+
if deployment_context in (
|
|
1269
|
+
DeploymentContext.DEVELOPMENT,
|
|
1270
|
+
DeploymentContext.EDITABLE_INSTALL,
|
|
1271
|
+
):
|
|
1272
|
+
# In development mode, unknown agents are likely system agents being tested
|
|
1273
|
+
return "system"
|
|
1274
|
+
if (
|
|
1275
|
+
deployment_context == DeploymentContext.PIPX_INSTALL
|
|
1276
|
+
and agent_name.count("-") <= 2
|
|
1277
|
+
and len(agent_name) <= 20
|
|
1278
|
+
):
|
|
1279
|
+
# In pipx mode, check if agent follows system naming patterns
|
|
1280
|
+
return "system"
|
|
1281
|
+
except Exception:
|
|
1282
|
+
pass
|
|
1283
|
+
|
|
1284
|
+
# Check if deployed to project-specific directory
|
|
1285
|
+
try:
|
|
1286
|
+
project_root = path_manager.project_root
|
|
1287
|
+
if str(deployed_agents_dir).startswith(str(project_root)):
|
|
1288
|
+
return "project"
|
|
1289
|
+
except Exception:
|
|
1290
|
+
pass
|
|
1291
|
+
|
|
1292
|
+
# Default inference based on naming patterns
|
|
1293
|
+
# System agents typically have simple names
|
|
1294
|
+
if "-" not in agent_name or agent_name.count("-") <= 1:
|
|
1295
|
+
return "system"
|
|
1296
|
+
|
|
1297
|
+
# Complex names are more likely to be user/project agents
|
|
1298
|
+
return "user"
|
|
1299
|
+
|
|
1300
|
+
def detect_orphaned_agents(
|
|
1301
|
+
self, deployed_agents_dir: Path, available_agents: Dict[str, Any]
|
|
1302
|
+
) -> List[Dict[str, Any]]:
|
|
1303
|
+
"""Detect deployed agents that don't have corresponding templates.
|
|
1304
|
+
|
|
1305
|
+
WHY: Orphaned agents can cause confusion with version warnings.
|
|
1306
|
+
This method identifies them so they can be handled appropriately.
|
|
1307
|
+
|
|
1308
|
+
Args:
|
|
1309
|
+
deployed_agents_dir: Directory containing deployed agents
|
|
1310
|
+
available_agents: Dictionary of available agents from all sources
|
|
1311
|
+
|
|
1312
|
+
Returns:
|
|
1313
|
+
List of orphaned agent information
|
|
1314
|
+
"""
|
|
1315
|
+
orphaned = []
|
|
1316
|
+
|
|
1317
|
+
if not deployed_agents_dir.exists():
|
|
1318
|
+
return orphaned
|
|
1319
|
+
|
|
1320
|
+
# Build a mapping of file stems to agent names for comparison
|
|
1321
|
+
# Since available_agents uses display names like "Code Analysis Agent"
|
|
1322
|
+
# but deployed files use stems like "code_analyzer"
|
|
1323
|
+
available_stems = set()
|
|
1324
|
+
stem_to_name = {}
|
|
1325
|
+
|
|
1326
|
+
for agent_name, agent_sources in available_agents.items():
|
|
1327
|
+
# Get the file path from the first source to extract the stem
|
|
1328
|
+
if (
|
|
1329
|
+
agent_sources
|
|
1330
|
+
and isinstance(agent_sources, list)
|
|
1331
|
+
and len(agent_sources) > 0
|
|
1332
|
+
):
|
|
1333
|
+
first_source = agent_sources[0]
|
|
1334
|
+
if "file_path" in first_source:
|
|
1335
|
+
file_path = Path(first_source["file_path"])
|
|
1336
|
+
stem = file_path.stem
|
|
1337
|
+
available_stems.add(stem)
|
|
1338
|
+
stem_to_name[stem] = agent_name
|
|
1339
|
+
|
|
1340
|
+
for deployed_file in deployed_agents_dir.glob("*.md"):
|
|
1341
|
+
agent_stem = deployed_file.stem
|
|
1342
|
+
|
|
1343
|
+
# Skip if this agent has a template (check by stem, not display name)
|
|
1344
|
+
if agent_stem in available_stems:
|
|
1345
|
+
continue
|
|
1346
|
+
|
|
1347
|
+
# This is an orphaned agent
|
|
1348
|
+
try:
|
|
1349
|
+
deployed_content = deployed_file.read_text()
|
|
1350
|
+
deployed_version, _, _ = (
|
|
1351
|
+
self.version_manager.extract_version_from_frontmatter(
|
|
1352
|
+
deployed_content
|
|
1353
|
+
)
|
|
1354
|
+
)
|
|
1355
|
+
version_str = self.version_manager.format_version_display(
|
|
1356
|
+
deployed_version
|
|
1357
|
+
)
|
|
1358
|
+
except Exception:
|
|
1359
|
+
version_str = "unknown"
|
|
1360
|
+
|
|
1361
|
+
orphaned.append(
|
|
1362
|
+
{"name": agent_stem, "file": str(deployed_file), "version": version_str}
|
|
1363
|
+
)
|
|
1364
|
+
|
|
1365
|
+
return orphaned
|
|
1366
|
+
|
|
1367
|
+
def _detect_orphaned_agents_simple(
|
|
1368
|
+
self, deployed_agents_dir: Path, agents_to_deploy: Dict[str, Path]
|
|
1369
|
+
) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]]]:
|
|
1370
|
+
"""Simple orphan detection that works with agents_to_deploy structure.
|
|
1371
|
+
|
|
1372
|
+
Args:
|
|
1373
|
+
deployed_agents_dir: Directory containing deployed agents
|
|
1374
|
+
agents_to_deploy: Dictionary mapping file stems to template paths
|
|
1375
|
+
|
|
1376
|
+
Returns:
|
|
1377
|
+
Tuple of (system_orphaned_agents, user_orphaned_agents)
|
|
1378
|
+
"""
|
|
1379
|
+
system_orphaned = []
|
|
1380
|
+
user_orphaned = []
|
|
1381
|
+
|
|
1382
|
+
if not deployed_agents_dir.exists():
|
|
1383
|
+
return system_orphaned, user_orphaned
|
|
1384
|
+
|
|
1385
|
+
# agents_to_deploy already contains file stems as keys
|
|
1386
|
+
available_stems = set(agents_to_deploy.keys())
|
|
1387
|
+
|
|
1388
|
+
for deployed_file in deployed_agents_dir.glob("*.md"):
|
|
1389
|
+
agent_stem = deployed_file.stem
|
|
1390
|
+
|
|
1391
|
+
# Skip if this agent has a template (check by stem)
|
|
1392
|
+
if agent_stem in available_stems:
|
|
1393
|
+
continue
|
|
1394
|
+
|
|
1395
|
+
# This is an orphaned agent - determine if it's user-created or system
|
|
1396
|
+
try:
|
|
1397
|
+
deployed_content = deployed_file.read_text()
|
|
1398
|
+
deployed_version, _, _ = (
|
|
1399
|
+
self.version_manager.extract_version_from_frontmatter(
|
|
1400
|
+
deployed_content
|
|
1401
|
+
)
|
|
1402
|
+
)
|
|
1403
|
+
version_str = self.version_manager.format_version_display(
|
|
1404
|
+
deployed_version
|
|
1405
|
+
)
|
|
1406
|
+
except Exception:
|
|
1407
|
+
version_str = "unknown"
|
|
1408
|
+
|
|
1409
|
+
orphan_info = {
|
|
1410
|
+
"name": agent_stem,
|
|
1411
|
+
"file": str(deployed_file),
|
|
1412
|
+
"version": version_str,
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
# Determine if this is a user-created agent
|
|
1416
|
+
if self._is_user_created_agent(deployed_file):
|
|
1417
|
+
user_orphaned.append(orphan_info)
|
|
1418
|
+
else:
|
|
1419
|
+
system_orphaned.append(orphan_info)
|
|
1420
|
+
|
|
1421
|
+
return system_orphaned, user_orphaned
|
|
1422
|
+
|
|
1423
|
+
def cleanup_orphaned_agents(
|
|
1424
|
+
self, deployed_agents_dir: Path, dry_run: bool = True
|
|
1425
|
+
) -> Dict[str, Any]:
|
|
1426
|
+
"""Clean up orphaned agents that don't have templates.
|
|
1427
|
+
|
|
1428
|
+
WHY: Orphaned agents can accumulate over time and cause confusion.
|
|
1429
|
+
This method provides a way to clean them up systematically.
|
|
1430
|
+
|
|
1431
|
+
Args:
|
|
1432
|
+
deployed_agents_dir: Directory containing deployed agents
|
|
1433
|
+
dry_run: If True, only report what would be removed
|
|
1434
|
+
|
|
1435
|
+
Returns:
|
|
1436
|
+
Dictionary with cleanup results
|
|
1437
|
+
"""
|
|
1438
|
+
results = {"orphaned": [], "removed": [], "errors": []}
|
|
1439
|
+
|
|
1440
|
+
# First, discover all available agents from all sources
|
|
1441
|
+
all_agents = self.discover_agents_from_all_sources()
|
|
1442
|
+
set(all_agents.keys())
|
|
1443
|
+
|
|
1444
|
+
# Detect orphaned agents
|
|
1445
|
+
orphaned = self.detect_orphaned_agents(deployed_agents_dir, all_agents)
|
|
1446
|
+
results["orphaned"] = orphaned
|
|
1447
|
+
|
|
1448
|
+
if not orphaned:
|
|
1449
|
+
self.logger.info("No orphaned agents found")
|
|
1450
|
+
return results
|
|
1451
|
+
|
|
1452
|
+
self.logger.info(f"Found {len(orphaned)} orphaned agent(s)")
|
|
1453
|
+
|
|
1454
|
+
for orphan in orphaned:
|
|
1455
|
+
agent_file = Path(orphan["file"])
|
|
1456
|
+
|
|
1457
|
+
if dry_run:
|
|
1458
|
+
self.logger.info(
|
|
1459
|
+
f" Would remove: {orphan['name']} v{orphan['version']}"
|
|
1460
|
+
)
|
|
1461
|
+
else:
|
|
1462
|
+
try:
|
|
1463
|
+
agent_file.unlink()
|
|
1464
|
+
results["removed"].append(orphan["name"])
|
|
1465
|
+
self.logger.info(
|
|
1466
|
+
f" Removed: {orphan['name']} v{orphan['version']}"
|
|
1467
|
+
)
|
|
1468
|
+
except Exception as e:
|
|
1469
|
+
error_msg = f"Failed to remove {orphan['name']}: {e}"
|
|
1470
|
+
results["errors"].append(error_msg)
|
|
1471
|
+
self.logger.error(f" {error_msg}")
|
|
1472
|
+
|
|
1473
|
+
if dry_run and orphaned:
|
|
1474
|
+
self.logger.info(
|
|
1475
|
+
"Run with dry_run=False to actually remove orphaned agents"
|
|
1476
|
+
)
|
|
1477
|
+
|
|
1478
|
+
return results
|