claude-mpm 3.9.11__py3-none-any.whl → 4.0.3__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/VERSION +1 -1
- claude_mpm/__init__.py +2 -2
- claude_mpm/__main__.py +3 -2
- claude_mpm/agents/__init__.py +85 -79
- claude_mpm/agents/agent_loader.py +464 -1003
- claude_mpm/agents/agent_loader_integration.py +45 -45
- claude_mpm/agents/agents_metadata.py +29 -30
- claude_mpm/agents/async_agent_loader.py +156 -138
- claude_mpm/agents/base_agent.json +1 -1
- claude_mpm/agents/base_agent_loader.py +179 -151
- claude_mpm/agents/frontmatter_validator.py +229 -130
- claude_mpm/agents/schema/agent_schema.json +1 -1
- claude_mpm/agents/system_agent_config.py +213 -147
- claude_mpm/agents/templates/__init__.py +13 -13
- claude_mpm/agents/templates/code_analyzer.json +2 -2
- claude_mpm/agents/templates/data_engineer.json +1 -1
- claude_mpm/agents/templates/documentation.json +23 -11
- claude_mpm/agents/templates/engineer.json +22 -6
- claude_mpm/agents/templates/memory_manager.json +1 -1
- claude_mpm/agents/templates/ops.json +2 -2
- claude_mpm/agents/templates/project_organizer.json +1 -1
- claude_mpm/agents/templates/qa.json +1 -1
- claude_mpm/agents/templates/refactoring_engineer.json +222 -0
- claude_mpm/agents/templates/research.json +20 -14
- claude_mpm/agents/templates/security.json +1 -1
- claude_mpm/agents/templates/ticketing.json +1 -1
- claude_mpm/agents/templates/version_control.json +1 -1
- claude_mpm/agents/templates/web_qa.json +3 -1
- claude_mpm/agents/templates/web_ui.json +2 -2
- claude_mpm/cli/__init__.py +79 -51
- claude_mpm/cli/__main__.py +3 -2
- claude_mpm/cli/commands/__init__.py +20 -20
- claude_mpm/cli/commands/agents.py +279 -247
- claude_mpm/cli/commands/aggregate.py +138 -157
- claude_mpm/cli/commands/cleanup.py +147 -147
- claude_mpm/cli/commands/config.py +93 -76
- claude_mpm/cli/commands/info.py +17 -16
- claude_mpm/cli/commands/mcp.py +140 -905
- claude_mpm/cli/commands/mcp_command_router.py +139 -0
- claude_mpm/cli/commands/mcp_config_commands.py +20 -0
- claude_mpm/cli/commands/mcp_install_commands.py +20 -0
- claude_mpm/cli/commands/mcp_server_commands.py +175 -0
- claude_mpm/cli/commands/mcp_tool_commands.py +34 -0
- claude_mpm/cli/commands/memory.py +239 -203
- claude_mpm/cli/commands/monitor.py +203 -81
- claude_mpm/cli/commands/run.py +380 -429
- claude_mpm/cli/commands/run_config_checker.py +160 -0
- claude_mpm/cli/commands/socketio_monitor.py +235 -0
- claude_mpm/cli/commands/tickets.py +305 -197
- claude_mpm/cli/parser.py +24 -1156
- claude_mpm/cli/parsers/__init__.py +29 -0
- claude_mpm/cli/parsers/agents_parser.py +136 -0
- claude_mpm/cli/parsers/base_parser.py +331 -0
- claude_mpm/cli/parsers/config_parser.py +85 -0
- claude_mpm/cli/parsers/mcp_parser.py +152 -0
- claude_mpm/cli/parsers/memory_parser.py +138 -0
- claude_mpm/cli/parsers/monitor_parser.py +104 -0
- claude_mpm/cli/parsers/run_parser.py +147 -0
- claude_mpm/cli/parsers/tickets_parser.py +203 -0
- claude_mpm/cli/ticket_cli.py +7 -3
- claude_mpm/cli/utils.py +55 -37
- 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 +38 -60
- claude_mpm/config/__init__.py +32 -25
- claude_mpm/config/agent_config.py +151 -119
- claude_mpm/config/experimental_features.py +71 -73
- claude_mpm/config/paths.py +94 -208
- claude_mpm/config/socketio_config.py +84 -73
- claude_mpm/constants.py +35 -18
- claude_mpm/core/__init__.py +9 -6
- claude_mpm/core/agent_name_normalizer.py +68 -71
- claude_mpm/core/agent_registry.py +372 -521
- claude_mpm/core/agent_session_manager.py +74 -63
- claude_mpm/core/base_service.py +116 -87
- claude_mpm/core/cache.py +119 -153
- claude_mpm/core/claude_runner.py +425 -1120
- claude_mpm/core/config.py +263 -168
- claude_mpm/core/config_aliases.py +69 -61
- claude_mpm/core/config_constants.py +292 -0
- claude_mpm/core/constants.py +57 -99
- claude_mpm/core/container.py +211 -178
- claude_mpm/core/exceptions.py +233 -89
- claude_mpm/core/factories.py +92 -54
- claude_mpm/core/framework_loader.py +378 -220
- claude_mpm/core/hook_manager.py +198 -83
- claude_mpm/core/hook_performance_config.py +136 -0
- claude_mpm/core/injectable_service.py +61 -55
- claude_mpm/core/interactive_session.py +165 -155
- claude_mpm/core/interfaces.py +221 -195
- claude_mpm/core/lazy.py +96 -96
- claude_mpm/core/logger.py +133 -107
- claude_mpm/core/logging_config.py +185 -157
- claude_mpm/core/minimal_framework_loader.py +20 -15
- claude_mpm/core/mixins.py +30 -29
- claude_mpm/core/oneshot_session.py +215 -181
- claude_mpm/core/optimized_agent_loader.py +134 -138
- claude_mpm/core/optimized_startup.py +159 -157
- claude_mpm/core/pm_hook_interceptor.py +85 -72
- claude_mpm/core/service_registry.py +103 -101
- claude_mpm/core/session_manager.py +97 -87
- claude_mpm/core/socketio_pool.py +212 -158
- claude_mpm/core/tool_access_control.py +58 -51
- claude_mpm/core/types.py +46 -24
- claude_mpm/core/typing_utils.py +166 -82
- claude_mpm/core/unified_agent_registry.py +721 -0
- claude_mpm/core/unified_config.py +550 -0
- claude_mpm/core/unified_paths.py +549 -0
- claude_mpm/dashboard/index.html +1 -1
- claude_mpm/dashboard/open_dashboard.py +51 -17
- claude_mpm/dashboard/static/css/dashboard.css +27 -8
- claude_mpm/dashboard/static/dist/components/agent-inference.js +2 -0
- claude_mpm/dashboard/static/dist/components/event-processor.js +2 -0
- claude_mpm/dashboard/static/dist/components/event-viewer.js +2 -0
- claude_mpm/dashboard/static/dist/components/export-manager.js +2 -0
- claude_mpm/dashboard/static/dist/components/file-tool-tracker.js +2 -0
- claude_mpm/dashboard/static/dist/components/hud-library-loader.js +2 -0
- claude_mpm/dashboard/static/dist/components/hud-manager.js +2 -0
- claude_mpm/dashboard/static/dist/components/hud-visualizer.js +2 -0
- claude_mpm/dashboard/static/dist/components/module-viewer.js +2 -0
- claude_mpm/dashboard/static/dist/components/session-manager.js +2 -0
- claude_mpm/dashboard/static/dist/components/socket-manager.js +2 -0
- claude_mpm/dashboard/static/dist/components/ui-state-manager.js +2 -0
- claude_mpm/dashboard/static/dist/components/working-directory.js +2 -0
- claude_mpm/dashboard/static/dist/dashboard.js +2 -0
- claude_mpm/dashboard/static/dist/socket-client.js +2 -0
- claude_mpm/dashboard/static/js/components/agent-inference.js +80 -76
- claude_mpm/dashboard/static/js/components/event-processor.js +71 -67
- claude_mpm/dashboard/static/js/components/event-viewer.js +74 -70
- claude_mpm/dashboard/static/js/components/export-manager.js +31 -28
- claude_mpm/dashboard/static/js/components/file-tool-tracker.js +106 -92
- claude_mpm/dashboard/static/js/components/hud-library-loader.js +11 -11
- claude_mpm/dashboard/static/js/components/hud-manager.js +73 -73
- claude_mpm/dashboard/static/js/components/hud-visualizer.js +163 -163
- claude_mpm/dashboard/static/js/components/module-viewer.js +305 -233
- claude_mpm/dashboard/static/js/components/session-manager.js +32 -29
- claude_mpm/dashboard/static/js/components/socket-manager.js +27 -20
- claude_mpm/dashboard/static/js/components/ui-state-manager.js +21 -18
- claude_mpm/dashboard/static/js/components/working-directory.js +74 -71
- claude_mpm/dashboard/static/js/dashboard.js +178 -453
- claude_mpm/dashboard/static/js/extension-error-handler.js +164 -0
- claude_mpm/dashboard/static/js/socket-client.js +120 -54
- claude_mpm/dashboard/templates/index.html +40 -50
- claude_mpm/experimental/cli_enhancements.py +60 -58
- claude_mpm/generators/__init__.py +1 -1
- claude_mpm/generators/agent_profile_generator.py +75 -65
- claude_mpm/hooks/__init__.py +1 -1
- claude_mpm/hooks/base_hook.py +33 -28
- claude_mpm/hooks/claude_hooks/__init__.py +1 -1
- claude_mpm/hooks/claude_hooks/connection_pool.py +120 -0
- claude_mpm/hooks/claude_hooks/event_handlers.py +743 -0
- claude_mpm/hooks/claude_hooks/hook_handler.py +415 -1331
- claude_mpm/hooks/claude_hooks/hook_wrapper.sh +4 -4
- claude_mpm/hooks/claude_hooks/memory_integration.py +221 -0
- claude_mpm/hooks/claude_hooks/response_tracking.py +348 -0
- claude_mpm/hooks/claude_hooks/tool_analysis.py +230 -0
- claude_mpm/hooks/memory_integration_hook.py +140 -100
- claude_mpm/hooks/tool_call_interceptor.py +89 -76
- claude_mpm/hooks/validation_hooks.py +57 -49
- claude_mpm/init.py +145 -121
- claude_mpm/models/__init__.py +9 -9
- claude_mpm/models/agent_definition.py +33 -23
- claude_mpm/models/agent_session.py +228 -200
- claude_mpm/scripts/__init__.py +1 -1
- claude_mpm/scripts/socketio_daemon.py +192 -75
- claude_mpm/scripts/socketio_server_manager.py +328 -0
- claude_mpm/scripts/start_activity_logging.py +25 -22
- claude_mpm/services/__init__.py +68 -43
- claude_mpm/services/agent_capabilities_service.py +271 -0
- claude_mpm/services/agents/__init__.py +23 -32
- claude_mpm/services/agents/deployment/__init__.py +3 -3
- claude_mpm/services/agents/deployment/agent_config_provider.py +310 -0
- claude_mpm/services/agents/deployment/agent_configuration_manager.py +359 -0
- claude_mpm/services/agents/deployment/agent_definition_factory.py +84 -0
- claude_mpm/services/agents/deployment/agent_deployment.py +415 -2113
- claude_mpm/services/agents/deployment/agent_discovery_service.py +387 -0
- claude_mpm/services/agents/deployment/agent_environment_manager.py +293 -0
- claude_mpm/services/agents/deployment/agent_filesystem_manager.py +387 -0
- claude_mpm/services/agents/deployment/agent_format_converter.py +453 -0
- claude_mpm/services/agents/deployment/agent_frontmatter_validator.py +161 -0
- claude_mpm/services/agents/deployment/agent_lifecycle_manager.py +345 -495
- claude_mpm/services/agents/deployment/agent_metrics_collector.py +279 -0
- claude_mpm/services/agents/deployment/agent_restore_handler.py +88 -0
- claude_mpm/services/agents/deployment/agent_template_builder.py +406 -0
- claude_mpm/services/agents/deployment/agent_validator.py +352 -0
- claude_mpm/services/agents/deployment/agent_version_manager.py +313 -0
- claude_mpm/services/agents/deployment/agent_versioning.py +6 -9
- claude_mpm/services/agents/deployment/agents_directory_resolver.py +79 -0
- claude_mpm/services/agents/deployment/async_agent_deployment.py +298 -234
- claude_mpm/services/agents/deployment/config/__init__.py +13 -0
- claude_mpm/services/agents/deployment/config/deployment_config.py +182 -0
- claude_mpm/services/agents/deployment/config/deployment_config_manager.py +200 -0
- claude_mpm/services/agents/deployment/deployment_config_loader.py +54 -0
- claude_mpm/services/agents/deployment/deployment_type_detector.py +124 -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 +73 -0
- claude_mpm/services/agents/deployment/facade/deployment_facade.py +270 -0
- claude_mpm/services/agents/deployment/facade/sync_deployment_executor.py +178 -0
- claude_mpm/services/agents/deployment/interface_adapter.py +227 -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/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 +159 -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 +195 -0
- claude_mpm/services/agents/deployment/pipeline/steps/base_step.py +119 -0
- claude_mpm/services/agents/deployment/pipeline/steps/configuration_step.py +79 -0
- claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +90 -0
- claude_mpm/services/agents/deployment/pipeline/steps/validation_step.py +100 -0
- claude_mpm/services/agents/deployment/processors/__init__.py +15 -0
- claude_mpm/services/agents/deployment/processors/agent_deployment_context.py +98 -0
- claude_mpm/services/agents/deployment/processors/agent_deployment_result.py +235 -0
- claude_mpm/services/agents/deployment/processors/agent_processor.py +258 -0
- claude_mpm/services/agents/deployment/refactored_agent_deployment_service.py +318 -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/strategies/__init__.py +25 -0
- claude_mpm/services/agents/deployment/strategies/base_strategy.py +119 -0
- claude_mpm/services/agents/deployment/strategies/project_strategy.py +150 -0
- claude_mpm/services/agents/deployment/strategies/strategy_selector.py +117 -0
- claude_mpm/services/agents/deployment/strategies/system_strategy.py +116 -0
- claude_mpm/services/agents/deployment/strategies/user_strategy.py +137 -0
- claude_mpm/services/agents/deployment/system_instructions_deployer.py +108 -0
- claude_mpm/services/agents/deployment/validation/__init__.py +19 -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 +299 -0
- claude_mpm/services/agents/deployment/validation/validation_result.py +226 -0
- claude_mpm/services/agents/loading/__init__.py +2 -2
- claude_mpm/services/agents/loading/agent_profile_loader.py +259 -229
- claude_mpm/services/agents/loading/base_agent_manager.py +90 -81
- claude_mpm/services/agents/loading/framework_agent_loader.py +154 -129
- claude_mpm/services/agents/management/__init__.py +2 -2
- claude_mpm/services/agents/management/agent_capabilities_generator.py +72 -58
- claude_mpm/services/agents/management/agent_management_service.py +209 -156
- claude_mpm/services/agents/memory/__init__.py +9 -6
- claude_mpm/services/agents/memory/agent_memory_manager.py +218 -1152
- claude_mpm/services/agents/memory/agent_persistence_service.py +20 -16
- claude_mpm/services/agents/memory/analyzer.py +430 -0
- claude_mpm/services/agents/memory/content_manager.py +376 -0
- claude_mpm/services/agents/memory/template_generator.py +468 -0
- claude_mpm/services/agents/registry/__init__.py +7 -10
- claude_mpm/services/agents/registry/deployed_agent_discovery.py +122 -97
- claude_mpm/services/agents/registry/modification_tracker.py +351 -285
- claude_mpm/services/async_session_logger.py +187 -153
- claude_mpm/services/claude_session_logger.py +87 -72
- claude_mpm/services/command_handler_service.py +217 -0
- claude_mpm/services/communication/__init__.py +3 -2
- claude_mpm/services/core/__init__.py +50 -97
- claude_mpm/services/core/base.py +60 -53
- claude_mpm/services/core/interfaces/__init__.py +188 -0
- claude_mpm/services/core/interfaces/agent.py +351 -0
- claude_mpm/services/core/interfaces/communication.py +343 -0
- claude_mpm/services/core/interfaces/infrastructure.py +413 -0
- claude_mpm/services/core/interfaces/service.py +434 -0
- claude_mpm/services/core/interfaces.py +19 -944
- claude_mpm/services/event_aggregator.py +208 -170
- claude_mpm/services/exceptions.py +387 -308
- claude_mpm/services/framework_claude_md_generator/__init__.py +75 -79
- claude_mpm/services/framework_claude_md_generator/content_assembler.py +69 -60
- claude_mpm/services/framework_claude_md_generator/content_validator.py +65 -61
- claude_mpm/services/framework_claude_md_generator/deployment_manager.py +68 -49
- claude_mpm/services/framework_claude_md_generator/section_generators/__init__.py +34 -34
- claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +25 -22
- claude_mpm/services/framework_claude_md_generator/section_generators/claude_pm_init.py +10 -10
- claude_mpm/services/framework_claude_md_generator/section_generators/core_responsibilities.py +4 -3
- 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 +4 -3
- 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 +4 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/troubleshooting.py +5 -4
- claude_mpm/services/framework_claude_md_generator/section_manager.py +28 -27
- claude_mpm/services/framework_claude_md_generator/version_manager.py +30 -28
- claude_mpm/services/hook_service.py +106 -114
- claude_mpm/services/infrastructure/__init__.py +7 -5
- claude_mpm/services/infrastructure/context_preservation.py +233 -199
- claude_mpm/services/infrastructure/daemon_manager.py +279 -0
- claude_mpm/services/infrastructure/logging.py +83 -76
- claude_mpm/services/infrastructure/monitoring.py +547 -404
- claude_mpm/services/mcp_gateway/__init__.py +30 -13
- claude_mpm/services/mcp_gateway/config/__init__.py +2 -2
- claude_mpm/services/mcp_gateway/config/config_loader.py +61 -56
- claude_mpm/services/mcp_gateway/config/config_schema.py +50 -41
- claude_mpm/services/mcp_gateway/config/configuration.py +82 -75
- claude_mpm/services/mcp_gateway/core/__init__.py +13 -20
- claude_mpm/services/mcp_gateway/core/base.py +80 -67
- claude_mpm/services/mcp_gateway/core/exceptions.py +60 -46
- claude_mpm/services/mcp_gateway/core/interfaces.py +87 -84
- claude_mpm/services/mcp_gateway/main.py +287 -137
- claude_mpm/services/mcp_gateway/registry/__init__.py +1 -1
- claude_mpm/services/mcp_gateway/registry/service_registry.py +97 -94
- claude_mpm/services/mcp_gateway/registry/tool_registry.py +135 -126
- claude_mpm/services/mcp_gateway/server/__init__.py +2 -2
- claude_mpm/services/mcp_gateway/server/mcp_gateway.py +105 -110
- claude_mpm/services/mcp_gateway/server/stdio_handler.py +105 -107
- claude_mpm/services/mcp_gateway/server/stdio_server.py +691 -0
- claude_mpm/services/mcp_gateway/tools/__init__.py +4 -2
- claude_mpm/services/mcp_gateway/tools/base_adapter.py +109 -119
- claude_mpm/services/mcp_gateway/tools/document_summarizer.py +283 -215
- claude_mpm/services/mcp_gateway/tools/hello_world.py +122 -120
- claude_mpm/services/mcp_gateway/tools/ticket_tools.py +652 -0
- claude_mpm/services/mcp_gateway/tools/unified_ticket_tool.py +606 -0
- claude_mpm/services/memory/__init__.py +2 -2
- claude_mpm/services/memory/builder.py +451 -362
- claude_mpm/services/memory/cache/__init__.py +2 -2
- claude_mpm/services/memory/cache/shared_prompt_cache.py +232 -194
- claude_mpm/services/memory/cache/simple_cache.py +107 -93
- claude_mpm/services/memory/indexed_memory.py +195 -193
- claude_mpm/services/memory/optimizer.py +267 -234
- claude_mpm/services/memory/router.py +571 -263
- claude_mpm/services/memory_hook_service.py +237 -0
- claude_mpm/services/port_manager.py +223 -0
- claude_mpm/services/project/__init__.py +3 -3
- claude_mpm/services/project/analyzer.py +451 -305
- claude_mpm/services/project/registry.py +262 -240
- claude_mpm/services/recovery_manager.py +287 -231
- claude_mpm/services/response_tracker.py +87 -67
- claude_mpm/services/runner_configuration_service.py +587 -0
- claude_mpm/services/session_management_service.py +304 -0
- claude_mpm/services/socketio/__init__.py +4 -4
- claude_mpm/services/socketio/client_proxy.py +174 -0
- claude_mpm/services/socketio/handlers/__init__.py +3 -3
- claude_mpm/services/socketio/handlers/base.py +44 -30
- claude_mpm/services/socketio/handlers/connection.py +145 -65
- claude_mpm/services/socketio/handlers/file.py +123 -108
- claude_mpm/services/socketio/handlers/git.py +607 -373
- claude_mpm/services/socketio/handlers/hook.py +170 -0
- claude_mpm/services/socketio/handlers/memory.py +4 -4
- claude_mpm/services/socketio/handlers/project.py +4 -4
- claude_mpm/services/socketio/handlers/registry.py +53 -38
- claude_mpm/services/socketio/server/__init__.py +18 -0
- claude_mpm/services/socketio/server/broadcaster.py +252 -0
- claude_mpm/services/socketio/server/core.py +399 -0
- claude_mpm/services/socketio/server/main.py +323 -0
- claude_mpm/services/socketio_client_manager.py +160 -133
- claude_mpm/services/socketio_server.py +36 -1885
- claude_mpm/services/subprocess_launcher_service.py +316 -0
- claude_mpm/services/system_instructions_service.py +258 -0
- claude_mpm/services/ticket_manager.py +19 -533
- claude_mpm/services/utility_service.py +285 -0
- claude_mpm/services/version_control/__init__.py +18 -21
- claude_mpm/services/version_control/branch_strategy.py +20 -10
- claude_mpm/services/version_control/conflict_resolution.py +37 -13
- claude_mpm/services/version_control/git_operations.py +52 -21
- claude_mpm/services/version_control/semantic_versioning.py +92 -53
- claude_mpm/services/version_control/version_parser.py +145 -125
- claude_mpm/services/version_service.py +270 -0
- claude_mpm/storage/__init__.py +2 -2
- claude_mpm/storage/state_storage.py +177 -181
- claude_mpm/ticket_wrapper.py +2 -2
- claude_mpm/utils/__init__.py +2 -2
- claude_mpm/utils/agent_dependency_loader.py +453 -243
- claude_mpm/utils/config_manager.py +157 -118
- claude_mpm/utils/console.py +1 -1
- claude_mpm/utils/dependency_cache.py +102 -107
- claude_mpm/utils/dependency_manager.py +52 -47
- claude_mpm/utils/dependency_strategies.py +131 -96
- claude_mpm/utils/environment_context.py +110 -102
- claude_mpm/utils/error_handler.py +75 -55
- claude_mpm/utils/file_utils.py +80 -67
- claude_mpm/utils/framework_detection.py +12 -11
- claude_mpm/utils/import_migration_example.py +12 -60
- claude_mpm/utils/imports.py +48 -45
- claude_mpm/utils/path_operations.py +100 -93
- claude_mpm/utils/robust_installer.py +172 -164
- claude_mpm/utils/session_logging.py +30 -23
- claude_mpm/utils/subprocess_utils.py +99 -61
- claude_mpm/validation/__init__.py +1 -1
- claude_mpm/validation/agent_validator.py +151 -111
- claude_mpm/validation/frontmatter_validator.py +92 -71
- {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.3.dist-info}/METADATA +27 -1
- claude_mpm-4.0.3.dist-info/RECORD +402 -0
- {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.3.dist-info}/entry_points.txt +1 -0
- {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.3.dist-info}/licenses/LICENSE +1 -1
- claude_mpm/cli/commands/run_guarded.py +0 -511
- claude_mpm/config/memory_guardian_config.py +0 -325
- claude_mpm/config/memory_guardian_yaml.py +0 -335
- claude_mpm/core/config_paths.py +0 -150
- claude_mpm/core/memory_aware_runner.py +0 -353
- claude_mpm/dashboard/static/js/dashboard-original.js +0 -4134
- claude_mpm/deployment_paths.py +0 -261
- claude_mpm/hooks/claude_hooks/hook_handler_fixed.py +0 -454
- claude_mpm/models/state_models.py +0 -433
- claude_mpm/services/agent/__init__.py +0 -24
- claude_mpm/services/agent/deployment.py +0 -2548
- claude_mpm/services/agent/management.py +0 -598
- claude_mpm/services/agent/registry.py +0 -813
- claude_mpm/services/agents/registry/agent_registry.py +0 -813
- claude_mpm/services/communication/socketio.py +0 -1935
- claude_mpm/services/communication/websocket.py +0 -479
- claude_mpm/services/framework_claude_md_generator.py +0 -624
- claude_mpm/services/health_monitor.py +0 -893
- claude_mpm/services/infrastructure/graceful_degradation.py +0 -616
- claude_mpm/services/infrastructure/health_monitor.py +0 -775
- claude_mpm/services/infrastructure/memory_dashboard.py +0 -479
- claude_mpm/services/infrastructure/memory_guardian.py +0 -944
- claude_mpm/services/infrastructure/restart_protection.py +0 -642
- claude_mpm/services/infrastructure/state_manager.py +0 -774
- claude_mpm/services/mcp_gateway/manager.py +0 -334
- claude_mpm/services/optimized_hook_service.py +0 -542
- claude_mpm/services/project_analyzer.py +0 -864
- claude_mpm/services/project_registry.py +0 -608
- 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 -510
- claude_mpm/utils/paths.py +0 -395
- claude_mpm/utils/platform_memory.py +0 -524
- claude_mpm-3.9.11.dist-info/RECORD +0 -306
- {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.3.dist-info}/WHEEL +0 -0
- {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.3.dist-info}/top_level.txt +0 -0
| @@ -1,3 +1,5 @@ | |
| 1 | 
            +
            from pathlib import Path
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            """Async Agent Deployment Service for high-performance parallel operations.
         | 
| 2 4 |  | 
| 3 5 | 
             
            This module provides async versions of agent deployment operations to dramatically
         | 
| @@ -19,114 +21,116 @@ DESIGN DECISIONS: | |
| 19 21 |  | 
| 20 22 | 
             
            import asyncio
         | 
| 21 23 | 
             
            import json
         | 
| 22 | 
            -
            import logging
         | 
| 23 24 | 
             
            import os
         | 
| 24 25 | 
             
            import time
         | 
| 25 | 
            -
            from pathlib import Path
         | 
| 26 | 
            -
            from typing import Dict, Any, List, Optional, Tuple
         | 
| 27 | 
            -
            import aiofiles
         | 
| 28 26 | 
             
            from concurrent.futures import ThreadPoolExecutor
         | 
| 27 | 
            +
            from typing import Any, Dict, List, Optional, Tuple
         | 
| 28 | 
            +
             | 
| 29 | 
            +
            import aiofiles
         | 
| 29 30 |  | 
| 30 | 
            -
            from claude_mpm.core.logger import get_logger
         | 
| 31 | 
            -
            from claude_mpm.constants import EnvironmentVars, Paths
         | 
| 32 31 | 
             
            from claude_mpm.config.paths import paths
         | 
| 32 | 
            +
            from claude_mpm.constants import EnvironmentVars, Paths
         | 
| 33 33 | 
             
            from claude_mpm.core.config import Config
         | 
| 34 | 
            +
            from claude_mpm.core.logger import get_logger
         | 
| 34 35 |  | 
| 35 36 |  | 
| 36 37 | 
             
            class AsyncAgentDeploymentService:
         | 
| 37 38 | 
             
                """Async service for high-performance agent deployment.
         | 
| 38 | 
            -
             | 
| 39 | 
            +
             | 
| 39 40 | 
             
                WHY: This async version provides:
         | 
| 40 41 | 
             
                - 50-70% reduction in startup time
         | 
| 41 42 | 
             
                - Parallel agent file discovery and processing
         | 
| 42 43 | 
             
                - Non-blocking I/O for all file operations
         | 
| 43 44 | 
             
                - Efficient batching of operations
         | 
| 44 45 | 
             
                - Seamless integration with existing sync code
         | 
| 45 | 
            -
             | 
| 46 | 
            +
             | 
| 46 47 | 
             
                PERFORMANCE METRICS:
         | 
| 47 48 | 
             
                - Sync discovery: ~500ms for 10 agents across 3 directories
         | 
| 48 49 | 
             
                - Async discovery: ~150ms for same (70% reduction)
         | 
| 49 50 | 
             
                - Sync JSON parsing: ~200ms for 10 files
         | 
| 50 51 | 
             
                - Async JSON parsing: ~50ms for same (75% reduction)
         | 
| 51 52 | 
             
                """
         | 
| 52 | 
            -
             | 
| 53 | 
            -
                def __init__( | 
| 54 | 
            -
             | 
| 55 | 
            -
             | 
| 53 | 
            +
             | 
| 54 | 
            +
                def __init__(
         | 
| 55 | 
            +
                    self,
         | 
| 56 | 
            +
                    templates_dir: Optional[Path] = None,
         | 
| 57 | 
            +
                    base_agent_path: Optional[Path] = None,
         | 
| 58 | 
            +
                    working_directory: Optional[Path] = None,
         | 
| 59 | 
            +
                ):
         | 
| 56 60 | 
             
                    """Initialize async agent deployment service.
         | 
| 57 | 
            -
             | 
| 61 | 
            +
             | 
| 58 62 | 
             
                    Args:
         | 
| 59 63 | 
             
                        templates_dir: Directory containing agent JSON files
         | 
| 60 64 | 
             
                        base_agent_path: Path to base_agent.md file
         | 
| 61 65 | 
             
                        working_directory: User's working directory (for project agents)
         | 
| 62 66 | 
             
                    """
         | 
| 63 67 | 
             
                    self.logger = get_logger(self.__class__.__name__)
         | 
| 64 | 
            -
             | 
| 68 | 
            +
             | 
| 65 69 | 
             
                    # Determine working directory
         | 
| 66 70 | 
             
                    if working_directory:
         | 
| 67 71 | 
             
                        self.working_directory = Path(working_directory)
         | 
| 68 | 
            -
                    elif  | 
| 69 | 
            -
                        self.working_directory = Path(os.environ[ | 
| 72 | 
            +
                    elif "CLAUDE_MPM_USER_PWD" in os.environ:
         | 
| 73 | 
            +
                        self.working_directory = Path(os.environ["CLAUDE_MPM_USER_PWD"])
         | 
| 70 74 | 
             
                    else:
         | 
| 71 75 | 
             
                        self.working_directory = Path.cwd()
         | 
| 72 | 
            -
             | 
| 76 | 
            +
             | 
| 73 77 | 
             
                    # Set template and base agent paths
         | 
| 74 78 | 
             
                    if templates_dir:
         | 
| 75 79 | 
             
                        self.templates_dir = Path(templates_dir)
         | 
| 76 80 | 
             
                    else:
         | 
| 77 81 | 
             
                        self.templates_dir = paths.agents_dir / "templates"
         | 
| 78 | 
            -
             | 
| 82 | 
            +
             | 
| 79 83 | 
             
                    if base_agent_path:
         | 
| 80 84 | 
             
                        self.base_agent_path = Path(base_agent_path)
         | 
| 81 85 | 
             
                    else:
         | 
| 82 86 | 
             
                        self.base_agent_path = paths.agents_dir / "base_agent.json"
         | 
| 83 | 
            -
             | 
| 87 | 
            +
             | 
| 84 88 | 
             
                    # Thread pool for CPU-bound JSON parsing
         | 
| 85 89 | 
             
                    self.executor = ThreadPoolExecutor(max_workers=4)
         | 
| 86 | 
            -
             | 
| 90 | 
            +
             | 
| 87 91 | 
             
                    # Performance metrics
         | 
| 88 92 | 
             
                    self._metrics = {
         | 
| 89 | 
            -
                         | 
| 90 | 
            -
                         | 
| 91 | 
            -
                         | 
| 93 | 
            +
                        "async_operations": 0,
         | 
| 94 | 
            +
                        "parallel_files_processed": 0,
         | 
| 95 | 
            +
                        "time_saved_ms": 0.0,
         | 
| 92 96 | 
             
                    }
         | 
| 93 | 
            -
             | 
| 94 | 
            -
                async def discover_agents_async( | 
| 97 | 
            +
             | 
| 98 | 
            +
                async def discover_agents_async(
         | 
| 99 | 
            +
                    self, directories: List[Path]
         | 
| 100 | 
            +
                ) -> Dict[str, List[Path]]:
         | 
| 95 101 | 
             
                    """Discover agent files across multiple directories in parallel.
         | 
| 96 | 
            -
             | 
| 102 | 
            +
             | 
| 97 103 | 
             
                    WHY: Parallel directory scanning reduces I/O wait time significantly.
         | 
| 98 104 | 
             
                    Each directory scan can take 50-100ms sequentially, but parallel
         | 
| 99 105 | 
             
                    scanning completes all directories in the time of the slowest one.
         | 
| 100 | 
            -
             | 
| 106 | 
            +
             | 
| 101 107 | 
             
                    Args:
         | 
| 102 108 | 
             
                        directories: List of directories to scan
         | 
| 103 | 
            -
             | 
| 109 | 
            +
             | 
| 104 110 | 
             
                    Returns:
         | 
| 105 111 | 
             
                        Dictionary mapping directory paths to lists of agent files
         | 
| 106 112 | 
             
                    """
         | 
| 107 113 | 
             
                    start_time = time.time()
         | 
| 108 | 
            -
             | 
| 114 | 
            +
             | 
| 109 115 | 
             
                    async def scan_directory(directory: Path) -> Tuple[str, List[Path]]:
         | 
| 110 116 | 
             
                        """Scan a single directory for agent files asynchronously."""
         | 
| 111 117 | 
             
                        if not directory.exists():
         | 
| 112 118 | 
             
                            return str(directory), []
         | 
| 113 | 
            -
             | 
| 119 | 
            +
             | 
| 114 120 | 
             
                        # Use asyncio to run glob in executor (since Path.glob is blocking)
         | 
| 115 121 | 
             
                        loop = asyncio.get_event_loop()
         | 
| 116 122 | 
             
                        files = await loop.run_in_executor(
         | 
| 117 | 
            -
                            self.executor,
         | 
| 118 | 
            -
                            lambda: list(directory.glob("*.json"))
         | 
| 123 | 
            +
                            self.executor, lambda: list(directory.glob("*.json"))
         | 
| 119 124 | 
             
                        )
         | 
| 120 | 
            -
             | 
| 125 | 
            +
             | 
| 121 126 | 
             
                        self.logger.debug(f"Found {len(files)} agents in {directory}")
         | 
| 122 127 | 
             
                        return str(directory), files
         | 
| 123 | 
            -
             | 
| 128 | 
            +
             | 
| 124 129 | 
             
                    # Scan all directories in parallel
         | 
| 125 130 | 
             
                    results = await asyncio.gather(
         | 
| 126 | 
            -
                        *[scan_directory(d) for d in directories],
         | 
| 127 | 
            -
                        return_exceptions=True
         | 
| 131 | 
            +
                        *[scan_directory(d) for d in directories], return_exceptions=True
         | 
| 128 132 | 
             
                    )
         | 
| 129 | 
            -
             | 
| 133 | 
            +
             | 
| 130 134 | 
             
                    # Process results
         | 
| 131 135 | 
             
                    discovered = {}
         | 
| 132 136 | 
             
                    for result in results:
         | 
| @@ -135,92 +139,92 @@ class AsyncAgentDeploymentService: | |
| 135 139 | 
             
                            continue
         | 
| 136 140 | 
             
                        dir_path, files = result
         | 
| 137 141 | 
             
                        discovered[dir_path] = files
         | 
| 138 | 
            -
             | 
| 142 | 
            +
             | 
| 139 143 | 
             
                    elapsed = (time.time() - start_time) * 1000
         | 
| 140 | 
            -
                    self._metrics[ | 
| 144 | 
            +
                    self._metrics["time_saved_ms"] += max(0, (len(directories) * 75) - elapsed)
         | 
| 141 145 | 
             
                    self.logger.info(f"Discovered agents in {elapsed:.1f}ms (parallel scan)")
         | 
| 142 | 
            -
             | 
| 146 | 
            +
             | 
| 143 147 | 
             
                    return discovered
         | 
| 144 | 
            -
             | 
| 145 | 
            -
                async def load_agent_files_async( | 
| 148 | 
            +
             | 
| 149 | 
            +
                async def load_agent_files_async(
         | 
| 150 | 
            +
                    self, file_paths: List[Path]
         | 
| 151 | 
            +
                ) -> List[Dict[str, Any]]:
         | 
| 146 152 | 
             
                    """Load and parse multiple agent files in parallel.
         | 
| 147 | 
            -
             | 
| 153 | 
            +
             | 
| 148 154 | 
             
                    WHY: JSON parsing is CPU-bound but file reading is I/O-bound.
         | 
| 149 155 | 
             
                    By separating these operations and parallelizing, we achieve:
         | 
| 150 156 | 
             
                    - Non-blocking file reads with aiofiles
         | 
| 151 157 | 
             
                    - Parallel JSON parsing in thread pool
         | 
| 152 158 | 
             
                    - Batch processing for efficiency
         | 
| 153 | 
            -
             | 
| 159 | 
            +
             | 
| 154 160 | 
             
                    Args:
         | 
| 155 161 | 
             
                        file_paths: List of agent file paths to load
         | 
| 156 | 
            -
             | 
| 162 | 
            +
             | 
| 157 163 | 
             
                    Returns:
         | 
| 158 164 | 
             
                        List of parsed agent configurations
         | 
| 159 165 | 
             
                    """
         | 
| 160 166 | 
             
                    start_time = time.time()
         | 
| 161 | 
            -
             | 
| 167 | 
            +
             | 
| 162 168 | 
             
                    async def load_single_file(file_path: Path) -> Optional[Dict[str, Any]]:
         | 
| 163 169 | 
             
                        """Load and parse a single agent file asynchronously."""
         | 
| 164 170 | 
             
                        try:
         | 
| 165 171 | 
             
                            # Non-blocking file read
         | 
| 166 | 
            -
                            async with aiofiles.open(file_path,  | 
| 172 | 
            +
                            async with aiofiles.open(file_path, "r") as f:
         | 
| 167 173 | 
             
                                content = await f.read()
         | 
| 168 | 
            -
             | 
| 174 | 
            +
             | 
| 169 175 | 
             
                            # Parse JSON in thread pool (CPU-bound)
         | 
| 170 176 | 
             
                            loop = asyncio.get_event_loop()
         | 
| 171 | 
            -
                            data = await loop.run_in_executor(
         | 
| 172 | 
            -
             | 
| 173 | 
            -
                                json.loads,
         | 
| 174 | 
            -
                                content
         | 
| 175 | 
            -
                            )
         | 
| 176 | 
            -
                            
         | 
| 177 | 
            +
                            data = await loop.run_in_executor(self.executor, json.loads, content)
         | 
| 178 | 
            +
             | 
| 177 179 | 
             
                            # Add file metadata
         | 
| 178 | 
            -
                            data[ | 
| 179 | 
            -
                            data[ | 
| 180 | 
            -
             | 
| 180 | 
            +
                            data["_source_file"] = str(file_path)
         | 
| 181 | 
            +
                            data["_agent_name"] = file_path.stem
         | 
| 182 | 
            +
             | 
| 181 183 | 
             
                            return data
         | 
| 182 | 
            -
             | 
| 184 | 
            +
             | 
| 183 185 | 
             
                        except Exception as e:
         | 
| 184 186 | 
             
                            self.logger.error(f"Failed to load {file_path}: {e}")
         | 
| 185 187 | 
             
                            return None
         | 
| 186 | 
            -
             | 
| 188 | 
            +
             | 
| 187 189 | 
             
                    # Load all files in parallel
         | 
| 188 190 | 
             
                    agents = await asyncio.gather(
         | 
| 189 | 
            -
                        *[load_single_file(fp) for fp in file_paths],
         | 
| 190 | 
            -
                        return_exceptions=False
         | 
| 191 | 
            +
                        *[load_single_file(fp) for fp in file_paths], return_exceptions=False
         | 
| 191 192 | 
             
                    )
         | 
| 192 | 
            -
             | 
| 193 | 
            +
             | 
| 193 194 | 
             
                    # Filter out None values (failed loads)
         | 
| 194 195 | 
             
                    valid_agents = [a for a in agents if a is not None]
         | 
| 195 | 
            -
             | 
| 196 | 
            +
             | 
| 196 197 | 
             
                    elapsed = (time.time() - start_time) * 1000
         | 
| 197 | 
            -
                    self._metrics[ | 
| 198 | 
            -
                    self._metrics[ | 
| 199 | 
            -
             | 
| 198 | 
            +
                    self._metrics["parallel_files_processed"] += len(file_paths)
         | 
| 199 | 
            +
                    self._metrics["async_operations"] += len(file_paths)
         | 
| 200 | 
            +
             | 
| 200 201 | 
             
                    self.logger.info(
         | 
| 201 202 | 
             
                        f"Loaded {len(valid_agents)}/{len(file_paths)} agents "
         | 
| 202 203 | 
             
                        f"in {elapsed:.1f}ms (parallel load)"
         | 
| 203 204 | 
             
                    )
         | 
| 204 | 
            -
             | 
| 205 | 
            +
             | 
| 205 206 | 
             
                    return valid_agents
         | 
| 206 | 
            -
             | 
| 207 | 
            -
                async def validate_agents_async( | 
| 207 | 
            +
             | 
| 208 | 
            +
                async def validate_agents_async(
         | 
| 209 | 
            +
                    self, agents: List[Dict[str, Any]]
         | 
| 210 | 
            +
                ) -> List[Dict[str, Any]]:
         | 
| 208 211 | 
             
                    """Validate multiple agents in parallel.
         | 
| 209 | 
            -
             | 
| 212 | 
            +
             | 
| 210 213 | 
             
                    WHY: Agent validation involves checking schemas and constraints.
         | 
| 211 214 | 
             
                    Parallel validation reduces time from O(n) to O(1) for the batch.
         | 
| 212 | 
            -
             | 
| 215 | 
            +
             | 
| 213 216 | 
             
                    Args:
         | 
| 214 217 | 
             
                        agents: List of agent configurations to validate
         | 
| 215 | 
            -
             | 
| 218 | 
            +
             | 
| 216 219 | 
             
                    Returns:
         | 
| 217 220 | 
             
                        List of valid agent configurations
         | 
| 218 221 | 
             
                    """
         | 
| 222 | 
            +
             | 
| 219 223 | 
             
                    async def validate_single(agent: Dict[str, Any]) -> Optional[Dict[str, Any]]:
         | 
| 220 224 | 
             
                        """Validate a single agent configuration."""
         | 
| 221 225 | 
             
                        try:
         | 
| 222 226 | 
             
                            # Basic validation (extend as needed)
         | 
| 223 | 
            -
                            required_fields = [ | 
| 227 | 
            +
                            required_fields = ["agent_id", "instructions"]
         | 
| 224 228 | 
             
                            if all(field in agent for field in required_fields):
         | 
| 225 229 | 
             
                                return agent
         | 
| 226 230 | 
             
                            else:
         | 
| @@ -233,239 +237,281 @@ class AsyncAgentDeploymentService: | |
| 233 237 | 
             
                        except Exception as e:
         | 
| 234 238 | 
             
                            self.logger.error(f"Validation error: {e}")
         | 
| 235 239 | 
             
                            return None
         | 
| 236 | 
            -
             | 
| 240 | 
            +
             | 
| 237 241 | 
             
                    # Validate all agents in parallel
         | 
| 238 242 | 
             
                    validated = await asyncio.gather(
         | 
| 239 | 
            -
                        *[validate_single(a) for a in agents],
         | 
| 240 | 
            -
                        return_exceptions=False
         | 
| 243 | 
            +
                        *[validate_single(a) for a in agents], return_exceptions=False
         | 
| 241 244 | 
             
                    )
         | 
| 242 | 
            -
             | 
| 245 | 
            +
             | 
| 243 246 | 
             
                    return [a for a in validated if a is not None]
         | 
| 244 | 
            -
             | 
| 245 | 
            -
                async def deploy_agents_async( | 
| 246 | 
            -
             | 
| 247 | 
            -
             | 
| 247 | 
            +
             | 
| 248 | 
            +
                async def deploy_agents_async(
         | 
| 249 | 
            +
                    self,
         | 
| 250 | 
            +
                    target_dir: Optional[Path] = None,
         | 
| 251 | 
            +
                    force_rebuild: bool = False,
         | 
| 252 | 
            +
                    config: Optional[Config] = None,
         | 
| 253 | 
            +
                ) -> Dict[str, Any]:
         | 
| 248 254 | 
             
                    """Deploy agents using async operations for maximum performance.
         | 
| 249 | 
            -
             | 
| 255 | 
            +
             | 
| 250 256 | 
             
                    WHY: This async deployment method provides:
         | 
| 251 257 | 
             
                    - Parallel file discovery across all tiers
         | 
| 252 258 | 
             
                    - Concurrent agent loading and validation
         | 
| 253 259 | 
             
                    - Batch processing for efficiency
         | 
| 254 260 | 
             
                    - 50-70% reduction in total deployment time
         | 
| 255 | 
            -
             | 
| 261 | 
            +
             | 
| 256 262 | 
             
                    Args:
         | 
| 257 263 | 
             
                        target_dir: Target directory for agents
         | 
| 258 264 | 
             
                        force_rebuild: Force rebuild even if agents exist
         | 
| 259 265 | 
             
                        config: Optional configuration object
         | 
| 260 | 
            -
             | 
| 266 | 
            +
             | 
| 261 267 | 
             
                    Returns:
         | 
| 262 268 | 
             
                        Dictionary with deployment results
         | 
| 263 269 | 
             
                    """
         | 
| 264 270 | 
             
                    start_time = time.time()
         | 
| 265 | 
            -
             | 
| 271 | 
            +
             | 
| 266 272 | 
             
                    # Load configuration
         | 
| 267 273 | 
             
                    if config is None:
         | 
| 268 274 | 
             
                        config = Config()
         | 
| 269 | 
            -
             | 
| 275 | 
            +
             | 
| 270 276 | 
             
                    # Get exclusion configuration
         | 
| 271 | 
            -
                    excluded_agents = config.get( | 
| 272 | 
            -
                    case_sensitive = config.get( | 
| 273 | 
            -
             | 
| 277 | 
            +
                    excluded_agents = config.get("agent_deployment.excluded_agents", [])
         | 
| 278 | 
            +
                    case_sensitive = config.get("agent_deployment.case_sensitive", False)
         | 
| 279 | 
            +
             | 
| 274 280 | 
             
                    results = {
         | 
| 275 281 | 
             
                        "deployed": [],
         | 
| 276 282 | 
             
                        "errors": [],
         | 
| 277 283 | 
             
                        "skipped": [],
         | 
| 278 284 | 
             
                        "updated": [],
         | 
| 279 | 
            -
                        "metrics": {
         | 
| 280 | 
            -
                            "async_mode": True,
         | 
| 281 | 
            -
                            "start_time": start_time
         | 
| 282 | 
            -
                        }
         | 
| 285 | 
            +
                        "metrics": {"async_mode": True, "start_time": start_time},
         | 
| 283 286 | 
             
                    }
         | 
| 284 | 
            -
             | 
| 287 | 
            +
             | 
| 285 288 | 
             
                    try:
         | 
| 286 289 | 
             
                        # Determine target directory
         | 
| 287 290 | 
             
                        if not target_dir:
         | 
| 288 291 | 
             
                            agents_dir = self.working_directory / ".claude" / "agents"
         | 
| 289 292 | 
             
                        else:
         | 
| 290 293 | 
             
                            agents_dir = self._resolve_agents_dir(target_dir)
         | 
| 291 | 
            -
             | 
| 292 | 
            -
                         | 
| 293 | 
            -
                        
         | 
| 294 | 
            +
             | 
| 295 | 
            +
                        # Create directory asynchronously
         | 
| 296 | 
            +
                        await self._create_directory_async(agents_dir)
         | 
| 297 | 
            +
             | 
| 294 298 | 
             
                        # Step 1: Discover agent files in parallel
         | 
| 295 299 | 
             
                        search_dirs = [
         | 
| 296 300 | 
             
                            self.working_directory / ".claude-mpm" / "agents",  # PROJECT
         | 
| 297 301 | 
             
                            Path.home() / ".claude-mpm" / "agents",  # USER
         | 
| 298 | 
            -
                            self.templates_dir  # SYSTEM
         | 
| 302 | 
            +
                            self.templates_dir,  # SYSTEM
         | 
| 299 303 | 
             
                        ]
         | 
| 300 | 
            -
             | 
| 304 | 
            +
             | 
| 301 305 | 
             
                        discovered = await self.discover_agents_async(
         | 
| 302 306 | 
             
                            [d for d in search_dirs if d.exists()]
         | 
| 303 307 | 
             
                        )
         | 
| 304 | 
            -
             | 
| 308 | 
            +
             | 
| 305 309 | 
             
                        # Step 2: Load all agent files in parallel
         | 
| 306 310 | 
             
                        all_files = []
         | 
| 307 311 | 
             
                        for files in discovered.values():
         | 
| 308 312 | 
             
                            all_files.extend(files)
         | 
| 309 | 
            -
             | 
| 313 | 
            +
             | 
| 310 314 | 
             
                        if not all_files:
         | 
| 311 315 | 
             
                            self.logger.warning("No agent files found")
         | 
| 312 316 | 
             
                            return results
         | 
| 313 | 
            -
             | 
| 317 | 
            +
             | 
| 314 318 | 
             
                        agents = await self.load_agent_files_async(all_files)
         | 
| 315 | 
            -
             | 
| 319 | 
            +
             | 
| 316 320 | 
             
                        # Step 3: Filter excluded agents
         | 
| 317 321 | 
             
                        filtered_agents = self._filter_excluded_agents(
         | 
| 318 322 | 
             
                            agents, excluded_agents, case_sensitive
         | 
| 319 323 | 
             
                        )
         | 
| 320 | 
            -
             | 
| 324 | 
            +
             | 
| 321 325 | 
             
                        # Step 4: Validate agents in parallel
         | 
| 322 326 | 
             
                        valid_agents = await self.validate_agents_async(filtered_agents)
         | 
| 323 | 
            -
             | 
| 324 | 
            -
                        # Step 5: Deploy valid agents  | 
| 325 | 
            -
                         | 
| 326 | 
            -
             | 
| 327 | 
            -
                            agent_name = agent.get('_agent_name', 'unknown')
         | 
| 328 | 
            -
                            target_file = agents_dir / f"{agent_name}.md"
         | 
| 329 | 
            -
                            
         | 
| 330 | 
            -
                            # Build markdown content (sync operation - could be parallelized)
         | 
| 331 | 
            -
                            content = self._build_agent_markdown_sync(agent)
         | 
| 332 | 
            -
                            
         | 
| 333 | 
            -
                            # Write file (could use aiofiles for true async)
         | 
| 334 | 
            -
                            target_file.write_text(content)
         | 
| 335 | 
            -
                            
         | 
| 336 | 
            -
                            results["deployed"].append(agent_name)
         | 
| 337 | 
            -
                            
         | 
| 327 | 
            +
             | 
| 328 | 
            +
                        # Step 5: Deploy valid agents using async file operations
         | 
| 329 | 
            +
                        await self._deploy_agents_async(valid_agents, agents_dir, results)
         | 
| 330 | 
            +
             | 
| 338 331 | 
             
                    except Exception as e:
         | 
| 339 332 | 
             
                        self.logger.error(f"Async deployment failed: {e}")
         | 
| 340 333 | 
             
                        results["errors"].append(str(e))
         | 
| 341 | 
            -
             | 
| 334 | 
            +
             | 
| 342 335 | 
             
                    # Calculate metrics
         | 
| 343 336 | 
             
                    elapsed = (time.time() - start_time) * 1000
         | 
| 344 337 | 
             
                    results["metrics"]["duration_ms"] = elapsed
         | 
| 345 338 | 
             
                    results["metrics"]["async_stats"] = self._metrics.copy()
         | 
| 346 | 
            -
             | 
| 339 | 
            +
             | 
| 347 340 | 
             
                    self.logger.info(
         | 
| 348 341 | 
             
                        f"Async deployment completed in {elapsed:.1f}ms "
         | 
| 349 342 | 
             
                        f"({len(results['deployed'])} deployed, "
         | 
| 350 343 | 
             
                        f"{len(results['errors'])} errors)"
         | 
| 351 344 | 
             
                    )
         | 
| 352 | 
            -
             | 
| 345 | 
            +
             | 
| 353 346 | 
             
                    return results
         | 
| 354 | 
            -
             | 
| 347 | 
            +
             | 
| 355 348 | 
             
                def _resolve_agents_dir(self, target_dir: Path) -> Path:
         | 
| 356 349 | 
             
                    """Resolve the agents directory from target directory."""
         | 
| 357 350 | 
             
                    target_dir = Path(target_dir)
         | 
| 358 | 
            -
             | 
| 351 | 
            +
             | 
| 359 352 | 
             
                    if target_dir.name == "agents":
         | 
| 360 353 | 
             
                        return target_dir
         | 
| 361 354 | 
             
                    elif target_dir.name in [".claude-mpm", ".claude"]:
         | 
| 362 355 | 
             
                        return target_dir / "agents"
         | 
| 363 356 | 
             
                    else:
         | 
| 364 357 | 
             
                        return target_dir / ".claude" / "agents"
         | 
| 365 | 
            -
             | 
| 366 | 
            -
                def _filter_excluded_agents( | 
| 367 | 
            -
             | 
| 368 | 
            -
             | 
| 358 | 
            +
             | 
| 359 | 
            +
                def _filter_excluded_agents(
         | 
| 360 | 
            +
                    self,
         | 
| 361 | 
            +
                    agents: List[Dict[str, Any]],
         | 
| 362 | 
            +
                    excluded_agents: List[str],
         | 
| 363 | 
            +
                    case_sensitive: bool,
         | 
| 364 | 
            +
                ) -> List[Dict[str, Any]]:
         | 
| 369 365 | 
             
                    """Filter out excluded agents from the list."""
         | 
| 370 366 | 
             
                    if not excluded_agents:
         | 
| 371 367 | 
             
                        return agents
         | 
| 372 | 
            -
             | 
| 368 | 
            +
             | 
| 373 369 | 
             
                    # Normalize exclusion list
         | 
| 374 370 | 
             
                    if not case_sensitive:
         | 
| 375 371 | 
             
                        excluded_agents = [a.lower() for a in excluded_agents]
         | 
| 376 | 
            -
             | 
| 372 | 
            +
             | 
| 377 373 | 
             
                    filtered = []
         | 
| 378 374 | 
             
                    for agent in agents:
         | 
| 379 | 
            -
                        agent_name = agent.get( | 
| 375 | 
            +
                        agent_name = agent.get("_agent_name", "")
         | 
| 380 376 | 
             
                        compare_name = agent_name if case_sensitive else agent_name.lower()
         | 
| 381 | 
            -
             | 
| 377 | 
            +
             | 
| 382 378 | 
             
                        if compare_name not in excluded_agents:
         | 
| 383 379 | 
             
                            filtered.append(agent)
         | 
| 384 380 | 
             
                        else:
         | 
| 385 381 | 
             
                            self.logger.debug(f"Excluding agent: {agent_name}")
         | 
| 386 | 
            -
             | 
| 382 | 
            +
             | 
| 387 383 | 
             
                    return filtered
         | 
| 388 | 
            -
             | 
| 384 | 
            +
             | 
| 385 | 
            +
                async def _create_directory_async(self, directory: Path) -> None:
         | 
| 386 | 
            +
                    """Create directory asynchronously using thread pool."""
         | 
| 387 | 
            +
                    loop = asyncio.get_event_loop()
         | 
| 388 | 
            +
                    await loop.run_in_executor(
         | 
| 389 | 
            +
                        self.executor, lambda: directory.mkdir(parents=True, exist_ok=True)
         | 
| 390 | 
            +
                    )
         | 
| 391 | 
            +
             | 
| 392 | 
            +
                async def _deploy_agents_async(
         | 
| 393 | 
            +
                    self, agents: List[Dict[str, Any]], agents_dir: Path, results: Dict[str, Any]
         | 
| 394 | 
            +
                ) -> None:
         | 
| 395 | 
            +
                    """Deploy agents using async file operations."""
         | 
| 396 | 
            +
             | 
| 397 | 
            +
                    async def deploy_single_agent(agent: Dict[str, Any]) -> Optional[str]:
         | 
| 398 | 
            +
                        """Deploy a single agent asynchronously."""
         | 
| 399 | 
            +
                        try:
         | 
| 400 | 
            +
                            agent_name = agent.get("_agent_name", "unknown")
         | 
| 401 | 
            +
                            target_file = agents_dir / f"{agent_name}.md"
         | 
| 402 | 
            +
             | 
| 403 | 
            +
                            # Build markdown content in thread pool (CPU-bound)
         | 
| 404 | 
            +
                            loop = asyncio.get_event_loop()
         | 
| 405 | 
            +
                            content = await loop.run_in_executor(
         | 
| 406 | 
            +
                                self.executor, self._build_agent_markdown_sync, agent
         | 
| 407 | 
            +
                            )
         | 
| 408 | 
            +
             | 
| 409 | 
            +
                            # Write file asynchronously
         | 
| 410 | 
            +
                            async with aiofiles.open(target_file, "w") as f:
         | 
| 411 | 
            +
                                await f.write(content)
         | 
| 412 | 
            +
             | 
| 413 | 
            +
                            return agent_name
         | 
| 414 | 
            +
             | 
| 415 | 
            +
                        except Exception as e:
         | 
| 416 | 
            +
                            self.logger.error(
         | 
| 417 | 
            +
                                f"Failed to deploy agent {agent.get('_agent_name', 'unknown')}: {e}"
         | 
| 418 | 
            +
                            )
         | 
| 419 | 
            +
                            return None
         | 
| 420 | 
            +
             | 
| 421 | 
            +
                    # Deploy all agents in parallel
         | 
| 422 | 
            +
                    deployed_names = await asyncio.gather(
         | 
| 423 | 
            +
                        *[deploy_single_agent(agent) for agent in agents], return_exceptions=False
         | 
| 424 | 
            +
                    )
         | 
| 425 | 
            +
             | 
| 426 | 
            +
                    # Update results with successful deployments
         | 
| 427 | 
            +
                    for name in deployed_names:
         | 
| 428 | 
            +
                        if name is not None:
         | 
| 429 | 
            +
                            results["deployed"].append(name)
         | 
| 430 | 
            +
             | 
| 389 431 | 
             
                def _build_agent_markdown_sync(self, agent_data: Dict[str, Any]) -> str:
         | 
| 390 432 | 
             
                    """Build agent markdown content matching the synchronous deployment format."""
         | 
| 391 433 | 
             
                    from datetime import datetime
         | 
| 392 | 
            -
             | 
| 434 | 
            +
             | 
| 393 435 | 
             
                    # Extract agent info from the loaded JSON data
         | 
| 394 | 
            -
                    agent_name = agent_data.get( | 
| 395 | 
            -
             | 
| 436 | 
            +
                    agent_name = agent_data.get("_agent_name", "unknown")
         | 
| 437 | 
            +
             | 
| 396 438 | 
             
                    # Extract proper agent_id from template data (not filename)
         | 
| 397 | 
            -
                    agent_id = agent_data.get( | 
| 398 | 
            -
             | 
| 439 | 
            +
                    agent_id = agent_data.get("agent_id", agent_name)
         | 
| 440 | 
            +
             | 
| 399 441 | 
             
                    # Handle both 'agent_version' (new format) and 'version' (old format)
         | 
| 400 | 
            -
                    agent_version = self._parse_version( | 
| 442 | 
            +
                    agent_version = self._parse_version(
         | 
| 443 | 
            +
                        agent_data.get("agent_version") or agent_data.get("version", "1.0.0")
         | 
| 444 | 
            +
                    )
         | 
| 401 445 | 
             
                    base_version = (0, 1, 0)  # Default base version for async deployment
         | 
| 402 | 
            -
             | 
| 446 | 
            +
             | 
| 403 447 | 
             
                    # Format version string as semantic version
         | 
| 404 448 | 
             
                    version_string = self._format_version_display(agent_version)
         | 
| 405 | 
            -
             | 
| 449 | 
            +
             | 
| 406 450 | 
             
                    # Extract metadata using the same logic as synchronous deployment
         | 
| 407 451 | 
             
                    # Check new format first (metadata.description), then old format
         | 
| 408 452 | 
             
                    description = (
         | 
| 409 | 
            -
                        agent_data.get( | 
| 410 | 
            -
                        agent_data.get( | 
| 411 | 
            -
                        agent_data.get( | 
| 412 | 
            -
                         | 
| 453 | 
            +
                        agent_data.get("metadata", {}).get("description")
         | 
| 454 | 
            +
                        or agent_data.get("configuration_fields", {}).get("description")
         | 
| 455 | 
            +
                        or agent_data.get("description")
         | 
| 456 | 
            +
                        or "Agent for specialized tasks"
         | 
| 413 457 | 
             
                    )
         | 
| 414 | 
            -
             | 
| 458 | 
            +
             | 
| 415 459 | 
             
                    # Get tags from new format (metadata.tags) or old format
         | 
| 416 460 | 
             
                    tags = (
         | 
| 417 | 
            -
                        agent_data.get( | 
| 418 | 
            -
                        agent_data.get( | 
| 419 | 
            -
                        agent_data.get( | 
| 420 | 
            -
                        [agent_id,  | 
| 461 | 
            +
                        agent_data.get("metadata", {}).get("tags")
         | 
| 462 | 
            +
                        or agent_data.get("configuration_fields", {}).get("tags")
         | 
| 463 | 
            +
                        or agent_data.get("tags")
         | 
| 464 | 
            +
                        or [agent_id, "mpm-framework"]
         | 
| 421 465 | 
             
                    )
         | 
| 422 | 
            -
             | 
| 466 | 
            +
             | 
| 423 467 | 
             
                    # Get tools from capabilities.tools in new format
         | 
| 424 468 | 
             
                    tools = (
         | 
| 425 | 
            -
                        agent_data.get( | 
| 426 | 
            -
                        agent_data.get( | 
| 427 | 
            -
                        ["Read", "Write", "Edit", "Grep", "Glob", "LS"]  # Default fallback
         | 
| 469 | 
            +
                        agent_data.get("capabilities", {}).get("tools")
         | 
| 470 | 
            +
                        or agent_data.get("configuration_fields", {}).get("tools")
         | 
| 471 | 
            +
                        or ["Read", "Write", "Edit", "Grep", "Glob", "LS"]  # Default fallback
         | 
| 428 472 | 
             
                    )
         | 
| 429 | 
            -
             | 
| 473 | 
            +
             | 
| 430 474 | 
             
                    # Get model from capabilities.model in new format
         | 
| 431 475 | 
             
                    model = (
         | 
| 432 | 
            -
                        agent_data.get( | 
| 433 | 
            -
                        agent_data.get( | 
| 434 | 
            -
                        "sonnet"  # Default fallback
         | 
| 476 | 
            +
                        agent_data.get("capabilities", {}).get("model")
         | 
| 477 | 
            +
                        or agent_data.get("configuration_fields", {}).get("model")
         | 
| 478 | 
            +
                        or "sonnet"  # Default fallback
         | 
| 435 479 | 
             
                    )
         | 
| 436 | 
            -
             | 
| 480 | 
            +
             | 
| 437 481 | 
             
                    # Simplify model name for Claude Code
         | 
| 438 482 | 
             
                    model_map = {
         | 
| 439 | 
            -
                         | 
| 440 | 
            -
                         | 
| 441 | 
            -
                         | 
| 442 | 
            -
                         | 
| 443 | 
            -
                         | 
| 444 | 
            -
                         | 
| 445 | 
            -
                         | 
| 483 | 
            +
                        "claude-4-sonnet-20250514": "sonnet",
         | 
| 484 | 
            +
                        "claude-sonnet-4-20250514": "sonnet",
         | 
| 485 | 
            +
                        "claude-opus-4-20250514": "opus",
         | 
| 486 | 
            +
                        "claude-3-opus-20240229": "opus",
         | 
| 487 | 
            +
                        "claude-3-haiku-20240307": "haiku",
         | 
| 488 | 
            +
                        "claude-3.5-sonnet": "sonnet",
         | 
| 489 | 
            +
                        "claude-3-sonnet": "sonnet",
         | 
| 446 490 | 
             
                    }
         | 
| 447 491 | 
             
                    # Better fallback: extract the model type (opus/sonnet/haiku) from the string
         | 
| 448 492 | 
             
                    if model not in model_map:
         | 
| 449 | 
            -
                        if  | 
| 450 | 
            -
                            model =  | 
| 451 | 
            -
                        elif  | 
| 452 | 
            -
                            model =  | 
| 453 | 
            -
                        elif  | 
| 454 | 
            -
                            model =  | 
| 493 | 
            +
                        if "opus" in model.lower():
         | 
| 494 | 
            +
                            model = "opus"
         | 
| 495 | 
            +
                        elif "sonnet" in model.lower():
         | 
| 496 | 
            +
                            model = "sonnet"
         | 
| 497 | 
            +
                        elif "haiku" in model.lower():
         | 
| 498 | 
            +
                            model = "haiku"
         | 
| 455 499 | 
             
                        else:
         | 
| 456 500 | 
             
                            # Last resort: try to extract from hyphenated format
         | 
| 457 | 
            -
                            model = model_map.get( | 
| 501 | 
            +
                            model = model_map.get(
         | 
| 502 | 
            +
                                model, model.split("-")[-1] if "-" in model else model
         | 
| 503 | 
            +
                            )
         | 
| 458 504 | 
             
                    else:
         | 
| 459 505 | 
             
                        model = model_map[model]
         | 
| 460 | 
            -
             | 
| 506 | 
            +
             | 
| 461 507 | 
             
                    # Convert tools list to comma-separated string for Claude Code compatibility
         | 
| 462 508 | 
             
                    # IMPORTANT: No spaces after commas - Claude Code requires exact format
         | 
| 463 | 
            -
                    tools_str =  | 
| 464 | 
            -
             | 
| 509 | 
            +
                    tools_str = ",".join(tools) if isinstance(tools, list) else str(tools)
         | 
| 510 | 
            +
             | 
| 465 511 | 
             
                    # Convert agent_id to Claude Code compatible name (replace underscores with hyphens)
         | 
| 466 512 | 
             
                    # Claude Code requires name to match pattern: ^[a-z0-9]+(-[a-z0-9]+)*$
         | 
| 467 | 
            -
                    claude_code_name = agent_id.replace( | 
| 468 | 
            -
             | 
| 513 | 
            +
                    claude_code_name = agent_id.replace("_", "-").lower()
         | 
| 514 | 
            +
             | 
| 469 515 | 
             
                    # Build frontmatter with only the fields Claude Code uses
         | 
| 470 516 | 
             
                    frontmatter_lines = [
         | 
| 471 517 | 
             
                        "---",
         | 
| @@ -475,84 +521,82 @@ class AsyncAgentDeploymentService: | |
| 475 521 | 
             
                        f"base_version: {self._format_version_display(base_version)}",
         | 
| 476 522 | 
             
                        f"author: claude-mpm",  # Identify as system agent for deployment
         | 
| 477 523 | 
             
                        f"tools: {tools_str}",
         | 
| 478 | 
            -
                        f"model: {model}"
         | 
| 524 | 
            +
                        f"model: {model}",
         | 
| 479 525 | 
             
                    ]
         | 
| 480 | 
            -
             | 
| 526 | 
            +
             | 
| 481 527 | 
             
                    # Add optional fields if present
         | 
| 482 528 | 
             
                    # Check for color in metadata section (new format) or root (old format)
         | 
| 483 | 
            -
                    color = (
         | 
| 484 | 
            -
                        agent_data.get('metadata', {}).get('color') or
         | 
| 485 | 
            -
                        agent_data.get('color')
         | 
| 486 | 
            -
                    )
         | 
| 529 | 
            +
                    color = agent_data.get("metadata", {}).get("color") or agent_data.get("color")
         | 
| 487 530 | 
             
                    if color:
         | 
| 488 531 | 
             
                        frontmatter_lines.append(f"color: {color}")
         | 
| 489 | 
            -
             | 
| 532 | 
            +
             | 
| 490 533 | 
             
                    frontmatter_lines.append("---")
         | 
| 491 534 | 
             
                    frontmatter_lines.append("")
         | 
| 492 535 | 
             
                    frontmatter_lines.append("")
         | 
| 493 | 
            -
             | 
| 494 | 
            -
                    frontmatter =  | 
| 495 | 
            -
             | 
| 536 | 
            +
             | 
| 537 | 
            +
                    frontmatter = "\n".join(frontmatter_lines)
         | 
| 538 | 
            +
             | 
| 496 539 | 
             
                    # Get the main content (instructions)
         | 
| 497 540 | 
             
                    # Check multiple possible locations for instructions
         | 
| 498 541 | 
             
                    content = (
         | 
| 499 | 
            -
                        agent_data.get( | 
| 500 | 
            -
                        agent_data.get( | 
| 501 | 
            -
                        agent_data.get( | 
| 502 | 
            -
                        f"You are the {agent_id} agent. Perform tasks related to {agent_data.get('description', 'your specialization')}."
         | 
| 542 | 
            +
                        agent_data.get("instructions")
         | 
| 543 | 
            +
                        or agent_data.get("narrative_fields", {}).get("instructions")
         | 
| 544 | 
            +
                        or agent_data.get("content")
         | 
| 545 | 
            +
                        or f"You are the {agent_id} agent. Perform tasks related to {agent_data.get('description', 'your specialization')}."
         | 
| 503 546 | 
             
                    )
         | 
| 504 | 
            -
             | 
| 547 | 
            +
             | 
| 505 548 | 
             
                    return frontmatter + content
         | 
| 506 | 
            -
             | 
| 549 | 
            +
             | 
| 507 550 | 
             
                def _parse_version(self, version_value: Any) -> tuple:
         | 
| 508 551 | 
             
                    """
         | 
| 509 552 | 
             
                    Parse version from various formats to semantic version tuple.
         | 
| 510 | 
            -
             | 
| 553 | 
            +
             | 
| 511 554 | 
             
                    Handles:
         | 
| 512 555 | 
             
                    - Integer values: 5 -> (0, 5, 0)
         | 
| 513 556 | 
             
                    - String integers: "5" -> (0, 5, 0)
         | 
| 514 557 | 
             
                    - Semantic versions: "2.1.0" -> (2, 1, 0)
         | 
| 515 558 | 
             
                    - Invalid formats: returns (0, 0, 0)
         | 
| 516 | 
            -
             | 
| 559 | 
            +
             | 
| 517 560 | 
             
                    Args:
         | 
| 518 561 | 
             
                        version_value: Version in various formats
         | 
| 519 | 
            -
             | 
| 562 | 
            +
             | 
| 520 563 | 
             
                    Returns:
         | 
| 521 564 | 
             
                        Tuple of (major, minor, patch) for comparison
         | 
| 522 565 | 
             
                    """
         | 
| 523 566 | 
             
                    if isinstance(version_value, int):
         | 
| 524 567 | 
             
                        # Legacy integer version - treat as minor version
         | 
| 525 568 | 
             
                        return (0, version_value, 0)
         | 
| 526 | 
            -
             | 
| 569 | 
            +
             | 
| 527 570 | 
             
                    if isinstance(version_value, str):
         | 
| 528 571 | 
             
                        # Try to parse as simple integer
         | 
| 529 572 | 
             
                        if version_value.isdigit():
         | 
| 530 573 | 
             
                            return (0, int(version_value), 0)
         | 
| 531 | 
            -
             | 
| 574 | 
            +
             | 
| 532 575 | 
             
                        # Try to parse semantic version (e.g., "2.1.0" or "v2.1.0")
         | 
| 533 576 | 
             
                        import re
         | 
| 534 | 
            -
             | 
| 577 | 
            +
             | 
| 578 | 
            +
                        sem_ver_match = re.match(r"^v?(\d+)\.(\d+)\.(\d+)", version_value)
         | 
| 535 579 | 
             
                        if sem_ver_match:
         | 
| 536 580 | 
             
                            major = int(sem_ver_match.group(1))
         | 
| 537 581 | 
             
                            minor = int(sem_ver_match.group(2))
         | 
| 538 582 | 
             
                            patch = int(sem_ver_match.group(3))
         | 
| 539 583 | 
             
                            return (major, minor, patch)
         | 
| 540 | 
            -
             | 
| 584 | 
            +
             | 
| 541 585 | 
             
                        # Try to extract first number from string as minor version
         | 
| 542 | 
            -
                        num_match = re.search(r | 
| 586 | 
            +
                        num_match = re.search(r"(\d+)", version_value)
         | 
| 543 587 | 
             
                        if num_match:
         | 
| 544 588 | 
             
                            return (0, int(num_match.group(1)), 0)
         | 
| 545 | 
            -
             | 
| 589 | 
            +
             | 
| 546 590 | 
             
                    # Default to 0.0.0 for invalid formats
         | 
| 547 591 | 
             
                    return (0, 0, 0)
         | 
| 548 | 
            -
             | 
| 592 | 
            +
             | 
| 549 593 | 
             
                def _format_version_display(self, version_tuple: tuple) -> str:
         | 
| 550 594 | 
             
                    """
         | 
| 551 595 | 
             
                    Format version tuple for display.
         | 
| 552 | 
            -
             | 
| 596 | 
            +
             | 
| 553 597 | 
             
                    Args:
         | 
| 554 598 | 
             
                        version_tuple: Tuple of (major, minor, patch)
         | 
| 555 | 
            -
             | 
| 599 | 
            +
             | 
| 556 600 | 
             
                    Returns:
         | 
| 557 601 | 
             
                        Formatted version string
         | 
| 558 602 | 
             
                    """
         | 
| @@ -562,61 +606,81 @@ class AsyncAgentDeploymentService: | |
| 562 606 | 
             
                    else:
         | 
| 563 607 | 
             
                        # Fallback for legacy format
         | 
| 564 608 | 
             
                        return str(version_tuple)
         | 
| 565 | 
            -
             | 
| 609 | 
            +
             | 
| 566 610 | 
             
                async def cleanup(self):
         | 
| 567 611 | 
             
                    """Clean up resources."""
         | 
| 568 612 | 
             
                    self.executor.shutdown(wait=False)
         | 
| 569 613 |  | 
| 570 614 |  | 
| 571 615 | 
             
            # Convenience function to run async deployment from sync code
         | 
| 572 | 
            -
            def deploy_agents_async_wrapper( | 
| 573 | 
            -
             | 
| 574 | 
            -
             | 
| 575 | 
            -
             | 
| 576 | 
            -
             | 
| 577 | 
            -
             | 
| 616 | 
            +
            def deploy_agents_async_wrapper(
         | 
| 617 | 
            +
                templates_dir: Optional[Path] = None,
         | 
| 618 | 
            +
                base_agent_path: Optional[Path] = None,
         | 
| 619 | 
            +
                working_directory: Optional[Path] = None,
         | 
| 620 | 
            +
                target_dir: Optional[Path] = None,
         | 
| 621 | 
            +
                force_rebuild: bool = False,
         | 
| 622 | 
            +
                config: Optional[Config] = None,
         | 
| 623 | 
            +
            ) -> Dict[str, Any]:
         | 
| 578 624 | 
             
                """Wrapper to run async deployment from synchronous code.
         | 
| 579 | 
            -
             | 
| 625 | 
            +
             | 
| 580 626 | 
             
                WHY: This wrapper allows the async deployment to be called from
         | 
| 581 627 | 
             
                existing synchronous code without requiring a full async refactor.
         | 
| 582 628 | 
             
                It manages the event loop and ensures proper cleanup.
         | 
| 583 | 
            -
             | 
| 629 | 
            +
             | 
| 584 630 | 
             
                Args:
         | 
| 585 631 | 
             
                    Same as AsyncAgentDeploymentService.deploy_agents_async()
         | 
| 586 | 
            -
             | 
| 632 | 
            +
             | 
| 587 633 | 
             
                Returns:
         | 
| 588 634 | 
             
                    Deployment results dictionary
         | 
| 589 635 | 
             
                """
         | 
| 636 | 
            +
             | 
| 590 637 | 
             
                async def run_deployment():
         | 
| 591 638 | 
             
                    service = AsyncAgentDeploymentService(
         | 
| 592 639 | 
             
                        templates_dir=templates_dir,
         | 
| 593 640 | 
             
                        base_agent_path=base_agent_path,
         | 
| 594 | 
            -
                        working_directory=working_directory
         | 
| 641 | 
            +
                        working_directory=working_directory,
         | 
| 595 642 | 
             
                    )
         | 
| 596 | 
            -
             | 
| 643 | 
            +
             | 
| 597 644 | 
             
                    try:
         | 
| 598 645 | 
             
                        results = await service.deploy_agents_async(
         | 
| 599 | 
            -
                            target_dir=target_dir,
         | 
| 600 | 
            -
                            force_rebuild=force_rebuild,
         | 
| 601 | 
            -
                            config=config
         | 
| 646 | 
            +
                            target_dir=target_dir, force_rebuild=force_rebuild, config=config
         | 
| 602 647 | 
             
                        )
         | 
| 603 648 | 
             
                        return results
         | 
| 604 649 | 
             
                    finally:
         | 
| 605 650 | 
             
                        await service.cleanup()
         | 
| 606 | 
            -
             | 
| 607 | 
            -
                # Run in event loop
         | 
| 651 | 
            +
             | 
| 652 | 
            +
                # Run in event loop with proper handling
         | 
| 608 653 | 
             
                try:
         | 
| 609 | 
            -
                    #  | 
| 610 | 
            -
                     | 
| 611 | 
            -
             | 
| 612 | 
            -
                        #  | 
| 654 | 
            +
                    # Check if we're already in an async context
         | 
| 655 | 
            +
                    try:
         | 
| 656 | 
            +
                        loop = asyncio.get_running_loop()
         | 
| 657 | 
            +
                        # We're in an async context, run in thread pool to avoid blocking
         | 
| 613 658 | 
             
                        import concurrent.futures
         | 
| 659 | 
            +
             | 
| 614 660 | 
             
                        with concurrent.futures.ThreadPoolExecutor() as executor:
         | 
| 615 661 | 
             
                            future = executor.submit(asyncio.run, run_deployment())
         | 
| 616 662 | 
             
                            return future.result()
         | 
| 617 | 
            -
                     | 
| 618 | 
            -
                        #  | 
| 619 | 
            -
                        return  | 
| 620 | 
            -
                except  | 
| 621 | 
            -
                    #  | 
| 622 | 
            -
                     | 
| 663 | 
            +
                    except RuntimeError:
         | 
| 664 | 
            +
                        # No running loop, safe to create new one
         | 
| 665 | 
            +
                        return asyncio.run(run_deployment())
         | 
| 666 | 
            +
                except Exception as e:
         | 
| 667 | 
            +
                    # Fallback to synchronous deployment if async fails
         | 
| 668 | 
            +
                    from claude_mpm.core.logger import get_logger
         | 
| 669 | 
            +
             | 
| 670 | 
            +
                    logger = get_logger("AsyncAgentDeploymentWrapper")
         | 
| 671 | 
            +
                    logger.warning(f"Async deployment failed, falling back to sync: {e}")
         | 
| 672 | 
            +
             | 
| 673 | 
            +
                    # Import and use synchronous deployment as fallback
         | 
| 674 | 
            +
                    from .agent_deployment import AgentDeploymentService
         | 
| 675 | 
            +
             | 
| 676 | 
            +
                    sync_service = AgentDeploymentService(
         | 
| 677 | 
            +
                        templates_dir=templates_dir,
         | 
| 678 | 
            +
                        base_agent_path=base_agent_path,
         | 
| 679 | 
            +
                        working_directory=working_directory,
         | 
| 680 | 
            +
                    )
         | 
| 681 | 
            +
                    return sync_service.deploy_agents(
         | 
| 682 | 
            +
                        target_dir=target_dir,
         | 
| 683 | 
            +
                        force_rebuild=force_rebuild,
         | 
| 684 | 
            +
                        config=config,
         | 
| 685 | 
            +
                        use_async=False,  # Explicitly disable async to avoid recursion
         | 
| 686 | 
            +
                    )
         |