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
| @@ -0,0 +1,387 @@ | |
| 1 | 
            +
            """Agent Discovery Service
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            This service handles the discovery, filtering, and metadata extraction of agent templates.
         | 
| 4 | 
            +
            Provides centralized logic for finding available agents and determining which should be deployed.
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            Extracted from AgentDeploymentService as part of the refactoring to improve
         | 
| 7 | 
            +
            maintainability and testability.
         | 
| 8 | 
            +
            """
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            import json
         | 
| 11 | 
            +
            import logging
         | 
| 12 | 
            +
            from pathlib import Path
         | 
| 13 | 
            +
            from typing import Any, Dict, List, Optional
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            from claude_mpm.core.config import Config
         | 
| 16 | 
            +
            from claude_mpm.core.logging_config import get_logger
         | 
| 17 | 
            +
             | 
| 18 | 
            +
             | 
| 19 | 
            +
            class AgentDiscoveryService:
         | 
| 20 | 
            +
                """Service for discovering and filtering agent templates.
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                This service handles:
         | 
| 23 | 
            +
                - Agent template discovery across multiple directories
         | 
| 24 | 
            +
                - Template filtering based on configuration
         | 
| 25 | 
            +
                - Agent metadata extraction and validation
         | 
| 26 | 
            +
                - Available agent listing and categorization
         | 
| 27 | 
            +
                """
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                def __init__(self, templates_dir: Path):
         | 
| 30 | 
            +
                    """Initialize the agent discovery service.
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                    Args:
         | 
| 33 | 
            +
                        templates_dir: Directory containing agent templates
         | 
| 34 | 
            +
                    """
         | 
| 35 | 
            +
                    self.logger = get_logger(__name__)
         | 
| 36 | 
            +
                    self.templates_dir = templates_dir
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                def list_available_agents(self) -> List[Dict[str, Any]]:
         | 
| 39 | 
            +
                    """
         | 
| 40 | 
            +
                    List all available agent templates with their metadata.
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                    Returns:
         | 
| 43 | 
            +
                        List of agent information dictionaries containing:
         | 
| 44 | 
            +
                        - name: Agent name
         | 
| 45 | 
            +
                        - description: Agent description
         | 
| 46 | 
            +
                        - version: Agent version
         | 
| 47 | 
            +
                        - tools: List of tools the agent uses
         | 
| 48 | 
            +
                        - specializations: Agent specializations
         | 
| 49 | 
            +
                        - file_path: Path to template file
         | 
| 50 | 
            +
                    """
         | 
| 51 | 
            +
                    agents = []
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                    if not self.templates_dir.exists():
         | 
| 54 | 
            +
                        self.logger.warning(
         | 
| 55 | 
            +
                            f"Templates directory does not exist: {self.templates_dir}"
         | 
| 56 | 
            +
                        )
         | 
| 57 | 
            +
                        return agents
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                    # Find all JSON template files
         | 
| 60 | 
            +
                    template_files = list(self.templates_dir.glob("*.json"))
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                    for template_file in template_files:
         | 
| 63 | 
            +
                        try:
         | 
| 64 | 
            +
                            agent_info = self._extract_agent_metadata(template_file)
         | 
| 65 | 
            +
                            if agent_info:
         | 
| 66 | 
            +
                                agents.append(agent_info)
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                        except Exception as e:
         | 
| 69 | 
            +
                            self.logger.error(
         | 
| 70 | 
            +
                                f"Failed to process template {template_file.name}: {e}"
         | 
| 71 | 
            +
                            )
         | 
| 72 | 
            +
                            continue
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                    # Sort by agent name for consistent ordering
         | 
| 75 | 
            +
                    agents.sort(key=lambda x: x.get("name", ""))
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                    self.logger.info(f"Discovered {len(agents)} available agent templates")
         | 
| 78 | 
            +
                    return agents
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                def get_filtered_templates(
         | 
| 81 | 
            +
                    self, excluded_agents: List[str], config: Optional[Config] = None
         | 
| 82 | 
            +
                ) -> List[Path]:
         | 
| 83 | 
            +
                    """
         | 
| 84 | 
            +
                    Get filtered list of template files based on configuration.
         | 
| 85 | 
            +
             | 
| 86 | 
            +
                    Args:
         | 
| 87 | 
            +
                        excluded_agents: List of agent names to exclude
         | 
| 88 | 
            +
                        config: Configuration object for additional filtering
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                    Returns:
         | 
| 91 | 
            +
                        List of template file paths to deploy
         | 
| 92 | 
            +
                    """
         | 
| 93 | 
            +
                    if not self.templates_dir.exists():
         | 
| 94 | 
            +
                        self.logger.error(f"Templates directory not found: {self.templates_dir}")
         | 
| 95 | 
            +
                        return []
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                    # Get all template files
         | 
| 98 | 
            +
                    template_files = list(self.templates_dir.glob("*.json"))
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                    if not template_files:
         | 
| 101 | 
            +
                        self.logger.warning(f"No agent templates found in {self.templates_dir}")
         | 
| 102 | 
            +
                        return []
         | 
| 103 | 
            +
             | 
| 104 | 
            +
                    # Apply exclusion filtering
         | 
| 105 | 
            +
                    filtered_files = []
         | 
| 106 | 
            +
                    excluded_count = 0
         | 
| 107 | 
            +
             | 
| 108 | 
            +
                    for template_file in template_files:
         | 
| 109 | 
            +
                        agent_name = template_file.stem
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                        # Check if agent is excluded
         | 
| 112 | 
            +
                        if self._is_agent_excluded(agent_name, excluded_agents, config):
         | 
| 113 | 
            +
                            excluded_count += 1
         | 
| 114 | 
            +
                            self.logger.debug(f"Excluding agent: {agent_name}")
         | 
| 115 | 
            +
                            continue
         | 
| 116 | 
            +
             | 
| 117 | 
            +
                        # Validate template file
         | 
| 118 | 
            +
                        if self._validate_template_file(template_file):
         | 
| 119 | 
            +
                            filtered_files.append(template_file)
         | 
| 120 | 
            +
                        else:
         | 
| 121 | 
            +
                            self.logger.warning(f"Invalid template file: {template_file.name}")
         | 
| 122 | 
            +
             | 
| 123 | 
            +
                    self.logger.info(
         | 
| 124 | 
            +
                        f"Found {len(template_files)} templates, excluded {excluded_count}, deploying {len(filtered_files)}"
         | 
| 125 | 
            +
                    )
         | 
| 126 | 
            +
                    return filtered_files
         | 
| 127 | 
            +
             | 
| 128 | 
            +
                def find_agent_template(self, agent_name: str) -> Optional[Path]:
         | 
| 129 | 
            +
                    """
         | 
| 130 | 
            +
                    Find template file for a specific agent.
         | 
| 131 | 
            +
             | 
| 132 | 
            +
                    Args:
         | 
| 133 | 
            +
                        agent_name: Name of the agent to find
         | 
| 134 | 
            +
             | 
| 135 | 
            +
                    Returns:
         | 
| 136 | 
            +
                        Path to template file if found, None otherwise
         | 
| 137 | 
            +
                    """
         | 
| 138 | 
            +
                    template_file = self.templates_dir / f"{agent_name}.json"
         | 
| 139 | 
            +
             | 
| 140 | 
            +
                    if template_file.exists():
         | 
| 141 | 
            +
                        if self._validate_template_file(template_file):
         | 
| 142 | 
            +
                            return template_file
         | 
| 143 | 
            +
                        else:
         | 
| 144 | 
            +
                            self.logger.error(f"Invalid template file: {template_file}")
         | 
| 145 | 
            +
                            return None
         | 
| 146 | 
            +
             | 
| 147 | 
            +
                    self.logger.error(f"Template not found for agent: {agent_name}")
         | 
| 148 | 
            +
                    return None
         | 
| 149 | 
            +
             | 
| 150 | 
            +
                def get_agent_categories(self) -> Dict[str, List[str]]:
         | 
| 151 | 
            +
                    """
         | 
| 152 | 
            +
                    Categorize available agents by type/specialization.
         | 
| 153 | 
            +
             | 
| 154 | 
            +
                    Returns:
         | 
| 155 | 
            +
                        Dictionary mapping categories to lists of agent names
         | 
| 156 | 
            +
                    """
         | 
| 157 | 
            +
                    categories = {}
         | 
| 158 | 
            +
                    agents = self.list_available_agents()
         | 
| 159 | 
            +
             | 
| 160 | 
            +
                    for agent in agents:
         | 
| 161 | 
            +
                        agent_name = agent.get("name", "unknown")
         | 
| 162 | 
            +
                        specializations = agent.get("specializations", [])
         | 
| 163 | 
            +
             | 
| 164 | 
            +
                        # Categorize by specializations
         | 
| 165 | 
            +
                        if specializations:
         | 
| 166 | 
            +
                            for spec in specializations:
         | 
| 167 | 
            +
                                if spec not in categories:
         | 
| 168 | 
            +
                                    categories[spec] = []
         | 
| 169 | 
            +
                                categories[spec].append(agent_name)
         | 
| 170 | 
            +
                        else:
         | 
| 171 | 
            +
                            # Default category for agents without specializations
         | 
| 172 | 
            +
                            if "general" not in categories:
         | 
| 173 | 
            +
                                categories["general"] = []
         | 
| 174 | 
            +
                            categories["general"].append(agent_name)
         | 
| 175 | 
            +
             | 
| 176 | 
            +
                    return categories
         | 
| 177 | 
            +
             | 
| 178 | 
            +
                def _extract_agent_metadata(self, template_file: Path) -> Optional[Dict[str, Any]]:
         | 
| 179 | 
            +
                    """
         | 
| 180 | 
            +
                    Extract metadata from an agent template file.
         | 
| 181 | 
            +
             | 
| 182 | 
            +
                    Args:
         | 
| 183 | 
            +
                        template_file: Path to the template file
         | 
| 184 | 
            +
             | 
| 185 | 
            +
                    Returns:
         | 
| 186 | 
            +
                        Dictionary with agent metadata or None if extraction fails
         | 
| 187 | 
            +
                    """
         | 
| 188 | 
            +
                    try:
         | 
| 189 | 
            +
                        # Read and parse template file
         | 
| 190 | 
            +
                        template_content = template_file.read_text()
         | 
| 191 | 
            +
                        template_data = json.loads(template_content)
         | 
| 192 | 
            +
             | 
| 193 | 
            +
                        # Extract basic metadata from the metadata section (per agent schema)
         | 
| 194 | 
            +
                        metadata = template_data.get("metadata", {})
         | 
| 195 | 
            +
                        capabilities = template_data.get("capabilities", {})
         | 
| 196 | 
            +
             | 
| 197 | 
            +
                        agent_info = {
         | 
| 198 | 
            +
                            "name": metadata.get("name", template_file.stem),
         | 
| 199 | 
            +
                            "description": metadata.get("description", "No description available"),
         | 
| 200 | 
            +
                            "version": template_data.get(
         | 
| 201 | 
            +
                                "agent_version", template_data.get("version", "1.0.0")
         | 
| 202 | 
            +
                            ),
         | 
| 203 | 
            +
                            "tools": capabilities.get("tools", []),
         | 
| 204 | 
            +
                            "specializations": metadata.get(
         | 
| 205 | 
            +
                                "tags", []
         | 
| 206 | 
            +
                            ),  # Use tags as specializations
         | 
| 207 | 
            +
                            "file": template_file.name,
         | 
| 208 | 
            +
                            "path": str(template_file),
         | 
| 209 | 
            +
                            "file_path": str(template_file),  # Keep for backward compatibility
         | 
| 210 | 
            +
                            "size": template_file.stat().st_size,
         | 
| 211 | 
            +
                            "model": capabilities.get("model", "sonnet"),
         | 
| 212 | 
            +
                            "author": metadata.get("author", "unknown"),
         | 
| 213 | 
            +
                        }
         | 
| 214 | 
            +
             | 
| 215 | 
            +
                        # Validate required fields
         | 
| 216 | 
            +
                        if not agent_info["name"]:
         | 
| 217 | 
            +
                            self.logger.warning(f"Template missing name: {template_file.name}")
         | 
| 218 | 
            +
                            return None
         | 
| 219 | 
            +
             | 
| 220 | 
            +
                        return agent_info
         | 
| 221 | 
            +
             | 
| 222 | 
            +
                    except json.JSONDecodeError as e:
         | 
| 223 | 
            +
                        self.logger.error(f"Invalid JSON in template {template_file.name}: {e}")
         | 
| 224 | 
            +
                        return None
         | 
| 225 | 
            +
                    except Exception as e:
         | 
| 226 | 
            +
                        self.logger.error(
         | 
| 227 | 
            +
                            f"Failed to extract metadata from {template_file.name}: {e}"
         | 
| 228 | 
            +
                        )
         | 
| 229 | 
            +
                        return None
         | 
| 230 | 
            +
             | 
| 231 | 
            +
                def _is_agent_excluded(
         | 
| 232 | 
            +
                    self,
         | 
| 233 | 
            +
                    agent_name: str,
         | 
| 234 | 
            +
                    excluded_agents: List[str],
         | 
| 235 | 
            +
                    config: Optional[Config] = None,
         | 
| 236 | 
            +
                ) -> bool:
         | 
| 237 | 
            +
                    """
         | 
| 238 | 
            +
                    Check if an agent should be excluded from deployment.
         | 
| 239 | 
            +
             | 
| 240 | 
            +
                    Args:
         | 
| 241 | 
            +
                        agent_name: Name of the agent to check
         | 
| 242 | 
            +
                        excluded_agents: List of explicitly excluded agents
         | 
| 243 | 
            +
                        config: Configuration object for additional exclusion rules
         | 
| 244 | 
            +
             | 
| 245 | 
            +
                    Returns:
         | 
| 246 | 
            +
                        True if agent should be excluded, False otherwise
         | 
| 247 | 
            +
                    """
         | 
| 248 | 
            +
                    # Check explicit exclusion list
         | 
| 249 | 
            +
                    if excluded_agents:
         | 
| 250 | 
            +
                        # Determine case sensitivity from config
         | 
| 251 | 
            +
                        case_sensitive = True
         | 
| 252 | 
            +
                        if config:
         | 
| 253 | 
            +
                            case_sensitive = config.get(
         | 
| 254 | 
            +
                                "agent_deployment.case_sensitive_exclusion", True
         | 
| 255 | 
            +
                            )
         | 
| 256 | 
            +
             | 
| 257 | 
            +
                        if case_sensitive:
         | 
| 258 | 
            +
                            if agent_name in excluded_agents:
         | 
| 259 | 
            +
                                return True
         | 
| 260 | 
            +
                        else:
         | 
| 261 | 
            +
                            # Case-insensitive comparison
         | 
| 262 | 
            +
                            agent_name_lower = agent_name.lower()
         | 
| 263 | 
            +
                            excluded_lower = [name.lower() for name in excluded_agents]
         | 
| 264 | 
            +
                            if agent_name_lower in excluded_lower:
         | 
| 265 | 
            +
                                return True
         | 
| 266 | 
            +
             | 
| 267 | 
            +
                    # Check for additional exclusion rules from config
         | 
| 268 | 
            +
                    if config:
         | 
| 269 | 
            +
                        # Check pattern-based exclusions
         | 
| 270 | 
            +
                        exclusion_patterns = config.get("agent_deployment.exclusion_patterns", [])
         | 
| 271 | 
            +
                        for pattern in exclusion_patterns:
         | 
| 272 | 
            +
                            if pattern in agent_name:
         | 
| 273 | 
            +
                                return True
         | 
| 274 | 
            +
             | 
| 275 | 
            +
                        # Check environment-specific exclusions
         | 
| 276 | 
            +
                        environment = config.get("environment", "development")
         | 
| 277 | 
            +
                        env_exclusions = config.get(
         | 
| 278 | 
            +
                            f"agent_deployment.{environment}_exclusions", []
         | 
| 279 | 
            +
                        )
         | 
| 280 | 
            +
                        if agent_name in env_exclusions:
         | 
| 281 | 
            +
                            return True
         | 
| 282 | 
            +
             | 
| 283 | 
            +
                    return False
         | 
| 284 | 
            +
             | 
| 285 | 
            +
                def _validate_template_file(self, template_file: Path) -> bool:
         | 
| 286 | 
            +
                    """
         | 
| 287 | 
            +
                    Validate that a template file is properly formatted.
         | 
| 288 | 
            +
             | 
| 289 | 
            +
                    Args:
         | 
| 290 | 
            +
                        template_file: Path to template file to validate
         | 
| 291 | 
            +
             | 
| 292 | 
            +
                    Returns:
         | 
| 293 | 
            +
                        True if template is valid, False otherwise
         | 
| 294 | 
            +
                    """
         | 
| 295 | 
            +
                    try:
         | 
| 296 | 
            +
                        # Check file exists and is readable
         | 
| 297 | 
            +
                        if not template_file.exists():
         | 
| 298 | 
            +
                            return False
         | 
| 299 | 
            +
             | 
| 300 | 
            +
                        # Parse JSON content
         | 
| 301 | 
            +
                        content = template_file.read_text()
         | 
| 302 | 
            +
                        template_data = json.loads(content)
         | 
| 303 | 
            +
             | 
| 304 | 
            +
                        # Check required fields in metadata section (per agent schema)
         | 
| 305 | 
            +
                        metadata = template_data.get("metadata", {})
         | 
| 306 | 
            +
                        required_fields = ["name", "description"]
         | 
| 307 | 
            +
                        for field in required_fields:
         | 
| 308 | 
            +
                            if field not in metadata:
         | 
| 309 | 
            +
                                self.logger.warning(
         | 
| 310 | 
            +
                                    f"Template {template_file.name} missing required field in metadata: {field}"
         | 
| 311 | 
            +
                                )
         | 
| 312 | 
            +
                                return False
         | 
| 313 | 
            +
             | 
| 314 | 
            +
                        # Validate agent ID format (Claude Code requirements)
         | 
| 315 | 
            +
                        # Use agent_id for validation, not the display name
         | 
| 316 | 
            +
                        agent_id = template_data.get("agent_id", "")
         | 
| 317 | 
            +
                        if not self._is_valid_agent_name(agent_id):
         | 
| 318 | 
            +
                            self.logger.warning(
         | 
| 319 | 
            +
                                f"Invalid agent ID format in {template_file.name}: {agent_id}"
         | 
| 320 | 
            +
                            )
         | 
| 321 | 
            +
                            return False
         | 
| 322 | 
            +
             | 
| 323 | 
            +
                        return True
         | 
| 324 | 
            +
             | 
| 325 | 
            +
                    except json.JSONDecodeError:
         | 
| 326 | 
            +
                        self.logger.error(f"Invalid JSON in template: {template_file.name}")
         | 
| 327 | 
            +
                        return False
         | 
| 328 | 
            +
                    except Exception as e:
         | 
| 329 | 
            +
                        self.logger.error(
         | 
| 330 | 
            +
                            f"Template validation failed for {template_file.name}: {e}"
         | 
| 331 | 
            +
                        )
         | 
| 332 | 
            +
                        return False
         | 
| 333 | 
            +
             | 
| 334 | 
            +
                def _is_valid_agent_name(self, agent_name: str) -> bool:
         | 
| 335 | 
            +
                    """
         | 
| 336 | 
            +
                    Validate agent name format according to Claude Code requirements.
         | 
| 337 | 
            +
             | 
| 338 | 
            +
                    Args:
         | 
| 339 | 
            +
                        agent_name: Agent name to validate
         | 
| 340 | 
            +
             | 
| 341 | 
            +
                    Returns:
         | 
| 342 | 
            +
                        True if name is valid, False otherwise
         | 
| 343 | 
            +
                    """
         | 
| 344 | 
            +
                    import re
         | 
| 345 | 
            +
             | 
| 346 | 
            +
                    # Claude Code requires lowercase letters, numbers, and hyphens only
         | 
| 347 | 
            +
                    # Must start with letter, no consecutive hyphens, no trailing hyphens
         | 
| 348 | 
            +
                    pattern = r"^[a-z][a-z0-9]*(-[a-z0-9]+)*$"
         | 
| 349 | 
            +
             | 
| 350 | 
            +
                    return bool(re.match(pattern, agent_name))
         | 
| 351 | 
            +
             | 
| 352 | 
            +
                def get_discovery_stats(self) -> Dict[str, Any]:
         | 
| 353 | 
            +
                    """
         | 
| 354 | 
            +
                    Get statistics about agent discovery.
         | 
| 355 | 
            +
             | 
| 356 | 
            +
                    Returns:
         | 
| 357 | 
            +
                        Dictionary with discovery statistics
         | 
| 358 | 
            +
                    """
         | 
| 359 | 
            +
                    stats = {
         | 
| 360 | 
            +
                        "total_templates": 0,
         | 
| 361 | 
            +
                        "valid_templates": 0,
         | 
| 362 | 
            +
                        "invalid_templates": 0,
         | 
| 363 | 
            +
                        "categories": {},
         | 
| 364 | 
            +
                        "templates_directory": str(self.templates_dir),
         | 
| 365 | 
            +
                        "directory_exists": self.templates_dir.exists(),
         | 
| 366 | 
            +
                    }
         | 
| 367 | 
            +
             | 
| 368 | 
            +
                    if not self.templates_dir.exists():
         | 
| 369 | 
            +
                        return stats
         | 
| 370 | 
            +
             | 
| 371 | 
            +
                    # Count template files
         | 
| 372 | 
            +
                    template_files = list(self.templates_dir.glob("*.json"))
         | 
| 373 | 
            +
                    stats["total_templates"] = len(template_files)
         | 
| 374 | 
            +
             | 
| 375 | 
            +
                    # Validate each template
         | 
| 376 | 
            +
                    valid_count = 0
         | 
| 377 | 
            +
                    for template_file in template_files:
         | 
| 378 | 
            +
                        if self._validate_template_file(template_file):
         | 
| 379 | 
            +
                            valid_count += 1
         | 
| 380 | 
            +
             | 
| 381 | 
            +
                    stats["valid_templates"] = valid_count
         | 
| 382 | 
            +
                    stats["invalid_templates"] = stats["total_templates"] - valid_count
         | 
| 383 | 
            +
             | 
| 384 | 
            +
                    # Get category distribution
         | 
| 385 | 
            +
                    stats["categories"] = self.get_agent_categories()
         | 
| 386 | 
            +
             | 
| 387 | 
            +
                    return stats
         | 
| @@ -0,0 +1,293 @@ | |
| 1 | 
            +
            """Agent Environment Manager Service
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            This service handles environment configuration for Claude agent discovery and execution.
         | 
| 4 | 
            +
            Manages environment variables, paths, and runtime configuration.
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            Extracted from AgentDeploymentService as part of the refactoring to improve
         | 
| 7 | 
            +
            maintainability and testability.
         | 
| 8 | 
            +
            """
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            import logging
         | 
| 11 | 
            +
            import os
         | 
| 12 | 
            +
            from pathlib import Path
         | 
| 13 | 
            +
            from typing import Any, Dict, Optional
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            from claude_mpm.core.logging_config import get_logger
         | 
| 16 | 
            +
             | 
| 17 | 
            +
             | 
| 18 | 
            +
            class AgentEnvironmentManager:
         | 
| 19 | 
            +
                """Service for managing Claude agent environment configuration.
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                This service handles:
         | 
| 22 | 
            +
                - Setting Claude environment variables
         | 
| 23 | 
            +
                - Managing agent discovery paths
         | 
| 24 | 
            +
                - Configuring runtime parameters
         | 
| 25 | 
            +
                - Environment validation and verification
         | 
| 26 | 
            +
                """
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                def __init__(self):
         | 
| 29 | 
            +
                    """Initialize the environment manager."""
         | 
| 30 | 
            +
                    self.logger = get_logger(__name__)
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                def set_claude_environment(
         | 
| 33 | 
            +
                    self, config_dir: Optional[Path] = None
         | 
| 34 | 
            +
                ) -> Dict[str, str]:
         | 
| 35 | 
            +
                    """
         | 
| 36 | 
            +
                    Set Claude environment variables for agent discovery.
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                    This configures the environment so Claude can discover and use deployed agents.
         | 
| 39 | 
            +
                    Essential for proper agent functionality in Claude Code.
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                    Args:
         | 
| 42 | 
            +
                        config_dir: Claude configuration directory (default: .claude/)
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                    Returns:
         | 
| 45 | 
            +
                        Dictionary of environment variables that were set
         | 
| 46 | 
            +
                    """
         | 
| 47 | 
            +
                    if not config_dir:
         | 
| 48 | 
            +
                        config_dir = Path.cwd() / ".claude"
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                    # Ensure config directory exists
         | 
| 51 | 
            +
                    config_dir.mkdir(parents=True, exist_ok=True)
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                    # Set environment variables for Claude agent discovery
         | 
| 54 | 
            +
                    env_vars = {
         | 
| 55 | 
            +
                        "CLAUDE_CONFIG_DIR": str(config_dir.absolute()),
         | 
| 56 | 
            +
                        "CLAUDE_MAX_PARALLEL_SUBAGENTS": "3",  # Reasonable default
         | 
| 57 | 
            +
                        "CLAUDE_TIMEOUT": "300",  # 5 minutes default timeout
         | 
| 58 | 
            +
                    }
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                    # Apply environment variables
         | 
| 61 | 
            +
                    for key, value in env_vars.items():
         | 
| 62 | 
            +
                        os.environ[key] = value
         | 
| 63 | 
            +
                        self.logger.debug(f"Set environment variable: {key}={value}")
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                    self.logger.info(
         | 
| 66 | 
            +
                        f"Claude environment configured for agent discovery in {config_dir}"
         | 
| 67 | 
            +
                    )
         | 
| 68 | 
            +
                    return env_vars
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                def get_current_environment(self) -> Dict[str, str]:
         | 
| 71 | 
            +
                    """
         | 
| 72 | 
            +
                    Get current Claude-related environment variables.
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                    Returns:
         | 
| 75 | 
            +
                        Dictionary of current Claude environment variables
         | 
| 76 | 
            +
                    """
         | 
| 77 | 
            +
                    claude_env_vars = {}
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                    # Check for Claude-specific environment variables
         | 
| 80 | 
            +
                    claude_prefixes = ["CLAUDE_", "ANTHROPIC_"]
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                    for key, value in os.environ.items():
         | 
| 83 | 
            +
                        if any(key.startswith(prefix) for prefix in claude_prefixes):
         | 
| 84 | 
            +
                            claude_env_vars[key] = value
         | 
| 85 | 
            +
             | 
| 86 | 
            +
                    return claude_env_vars
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                def validate_environment(self, config_dir: Optional[Path] = None) -> Dict[str, Any]:
         | 
| 89 | 
            +
                    """
         | 
| 90 | 
            +
                    Validate the current Claude environment configuration.
         | 
| 91 | 
            +
             | 
| 92 | 
            +
                    Args:
         | 
| 93 | 
            +
                        config_dir: Claude configuration directory to validate
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                    Returns:
         | 
| 96 | 
            +
                        Dictionary with validation results
         | 
| 97 | 
            +
                    """
         | 
| 98 | 
            +
                    if not config_dir:
         | 
| 99 | 
            +
                        config_dir = Path.cwd() / ".claude"
         | 
| 100 | 
            +
             | 
| 101 | 
            +
                    validation_results = {
         | 
| 102 | 
            +
                        "valid": True,
         | 
| 103 | 
            +
                        "warnings": [],
         | 
| 104 | 
            +
                        "errors": [],
         | 
| 105 | 
            +
                        "config_dir_exists": False,
         | 
| 106 | 
            +
                        "agents_dir_exists": False,
         | 
| 107 | 
            +
                        "environment_variables": {},
         | 
| 108 | 
            +
                        "recommendations": [],
         | 
| 109 | 
            +
                    }
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                    # Check if config directory exists
         | 
| 112 | 
            +
                    if config_dir.exists():
         | 
| 113 | 
            +
                        validation_results["config_dir_exists"] = True
         | 
| 114 | 
            +
                        self.logger.debug(f"Config directory exists: {config_dir}")
         | 
| 115 | 
            +
                    else:
         | 
| 116 | 
            +
                        validation_results["valid"] = False
         | 
| 117 | 
            +
                        validation_results["errors"].append(
         | 
| 118 | 
            +
                            f"Config directory does not exist: {config_dir}"
         | 
| 119 | 
            +
                        )
         | 
| 120 | 
            +
             | 
| 121 | 
            +
                    # Check if agents directory exists
         | 
| 122 | 
            +
                    agents_dir = config_dir / "agents"
         | 
| 123 | 
            +
                    if agents_dir.exists():
         | 
| 124 | 
            +
                        validation_results["agents_dir_exists"] = True
         | 
| 125 | 
            +
                        self.logger.debug(f"Agents directory exists: {agents_dir}")
         | 
| 126 | 
            +
                    else:
         | 
| 127 | 
            +
                        validation_results["warnings"].append(
         | 
| 128 | 
            +
                            f"Agents directory does not exist: {agents_dir}"
         | 
| 129 | 
            +
                        )
         | 
| 130 | 
            +
             | 
| 131 | 
            +
                    # Check environment variables
         | 
| 132 | 
            +
                    current_env = self.get_current_environment()
         | 
| 133 | 
            +
                    validation_results["environment_variables"] = current_env
         | 
| 134 | 
            +
             | 
| 135 | 
            +
                    # Validate required environment variables
         | 
| 136 | 
            +
                    required_vars = ["CLAUDE_CONFIG_DIR"]
         | 
| 137 | 
            +
                    for var in required_vars:
         | 
| 138 | 
            +
                        if var not in current_env:
         | 
| 139 | 
            +
                            validation_results["warnings"].append(
         | 
| 140 | 
            +
                                f"Missing environment variable: {var}"
         | 
| 141 | 
            +
                            )
         | 
| 142 | 
            +
                            validation_results["recommendations"].append(
         | 
| 143 | 
            +
                                f"Set {var} to point to your Claude config directory"
         | 
| 144 | 
            +
                            )
         | 
| 145 | 
            +
             | 
| 146 | 
            +
                    # Check if CLAUDE_CONFIG_DIR points to the right place
         | 
| 147 | 
            +
                    if "CLAUDE_CONFIG_DIR" in current_env:
         | 
| 148 | 
            +
                        env_config_dir = Path(current_env["CLAUDE_CONFIG_DIR"])
         | 
| 149 | 
            +
                        if env_config_dir != config_dir.absolute():
         | 
| 150 | 
            +
                            validation_results["warnings"].append(
         | 
| 151 | 
            +
                                f"CLAUDE_CONFIG_DIR ({env_config_dir}) doesn't match expected path ({config_dir.absolute()})"
         | 
| 152 | 
            +
                            )
         | 
| 153 | 
            +
             | 
| 154 | 
            +
                    # Performance recommendations
         | 
| 155 | 
            +
                    if "CLAUDE_MAX_PARALLEL_SUBAGENTS" not in current_env:
         | 
| 156 | 
            +
                        validation_results["recommendations"].append(
         | 
| 157 | 
            +
                            "Consider setting CLAUDE_MAX_PARALLEL_SUBAGENTS for better performance"
         | 
| 158 | 
            +
                        )
         | 
| 159 | 
            +
             | 
| 160 | 
            +
                    if "CLAUDE_TIMEOUT" not in current_env:
         | 
| 161 | 
            +
                        validation_results["recommendations"].append(
         | 
| 162 | 
            +
                            "Consider setting CLAUDE_TIMEOUT to prevent long-running operations"
         | 
| 163 | 
            +
                        )
         | 
| 164 | 
            +
             | 
| 165 | 
            +
                    return validation_results
         | 
| 166 | 
            +
             | 
| 167 | 
            +
                def setup_development_environment(
         | 
| 168 | 
            +
                    self, config_dir: Optional[Path] = None
         | 
| 169 | 
            +
                ) -> Dict[str, Any]:
         | 
| 170 | 
            +
                    """
         | 
| 171 | 
            +
                    Set up a complete development environment for Claude agents.
         | 
| 172 | 
            +
             | 
| 173 | 
            +
                    Args:
         | 
| 174 | 
            +
                        config_dir: Claude configuration directory
         | 
| 175 | 
            +
             | 
| 176 | 
            +
                    Returns:
         | 
| 177 | 
            +
                        Dictionary with setup results
         | 
| 178 | 
            +
                    """
         | 
| 179 | 
            +
                    if not config_dir:
         | 
| 180 | 
            +
                        config_dir = Path.cwd() / ".claude"
         | 
| 181 | 
            +
             | 
| 182 | 
            +
                    setup_results = {
         | 
| 183 | 
            +
                        "success": True,
         | 
| 184 | 
            +
                        "created_directories": [],
         | 
| 185 | 
            +
                        "set_environment_variables": {},
         | 
| 186 | 
            +
                        "errors": [],
         | 
| 187 | 
            +
                    }
         | 
| 188 | 
            +
             | 
| 189 | 
            +
                    try:
         | 
| 190 | 
            +
                        # Create necessary directories
         | 
| 191 | 
            +
                        directories_to_create = [
         | 
| 192 | 
            +
                            config_dir,
         | 
| 193 | 
            +
                            config_dir / "agents",
         | 
| 194 | 
            +
                            config_dir / "logs",
         | 
| 195 | 
            +
                            config_dir / "cache",
         | 
| 196 | 
            +
                        ]
         | 
| 197 | 
            +
             | 
| 198 | 
            +
                        for directory in directories_to_create:
         | 
| 199 | 
            +
                            if not directory.exists():
         | 
| 200 | 
            +
                                directory.mkdir(parents=True, exist_ok=True)
         | 
| 201 | 
            +
                                setup_results["created_directories"].append(str(directory))
         | 
| 202 | 
            +
                                self.logger.info(f"Created directory: {directory}")
         | 
| 203 | 
            +
             | 
| 204 | 
            +
                        # Set up environment variables
         | 
| 205 | 
            +
                        env_vars = self.set_claude_environment(config_dir)
         | 
| 206 | 
            +
                        setup_results["set_environment_variables"] = env_vars
         | 
| 207 | 
            +
             | 
| 208 | 
            +
                        # Create a basic .gitignore for the config directory
         | 
| 209 | 
            +
                        gitignore_path = config_dir / ".gitignore"
         | 
| 210 | 
            +
                        if not gitignore_path.exists():
         | 
| 211 | 
            +
                            gitignore_content = """# Claude configuration files
         | 
| 212 | 
            +
            logs/
         | 
| 213 | 
            +
            cache/
         | 
| 214 | 
            +
            *.log
         | 
| 215 | 
            +
            *.tmp
         | 
| 216 | 
            +
             | 
| 217 | 
            +
            # Keep agents directory but ignore specific files if needed
         | 
| 218 | 
            +
            # agents/
         | 
| 219 | 
            +
            """
         | 
| 220 | 
            +
                            gitignore_path.write_text(gitignore_content)
         | 
| 221 | 
            +
                            self.logger.info(f"Created .gitignore: {gitignore_path}")
         | 
| 222 | 
            +
             | 
| 223 | 
            +
                    except Exception as e:
         | 
| 224 | 
            +
                        setup_results["success"] = False
         | 
| 225 | 
            +
                        setup_results["errors"].append(str(e))
         | 
| 226 | 
            +
                        self.logger.error(f"Failed to set up development environment: {e}")
         | 
| 227 | 
            +
             | 
| 228 | 
            +
                    return setup_results
         | 
| 229 | 
            +
             | 
| 230 | 
            +
                def get_environment_info(self) -> Dict[str, Any]:
         | 
| 231 | 
            +
                    """
         | 
| 232 | 
            +
                    Get comprehensive information about the current environment.
         | 
| 233 | 
            +
             | 
| 234 | 
            +
                    Returns:
         | 
| 235 | 
            +
                        Dictionary with environment information
         | 
| 236 | 
            +
                    """
         | 
| 237 | 
            +
                    current_env = self.get_current_environment()
         | 
| 238 | 
            +
             | 
| 239 | 
            +
                    info = {
         | 
| 240 | 
            +
                        "claude_environment_variables": current_env,
         | 
| 241 | 
            +
                        "python_path": os.environ.get("PYTHONPATH", "Not set"),
         | 
| 242 | 
            +
                        "current_working_directory": str(Path.cwd()),
         | 
| 243 | 
            +
                        "user_home_directory": str(Path.home()),
         | 
| 244 | 
            +
                        "claude_config_locations": self._find_claude_config_locations(),
         | 
| 245 | 
            +
                    }
         | 
| 246 | 
            +
             | 
| 247 | 
            +
                    return info
         | 
| 248 | 
            +
             | 
| 249 | 
            +
                def cleanup_environment(self) -> Dict[str, Any]:
         | 
| 250 | 
            +
                    """
         | 
| 251 | 
            +
                    Clean up Claude environment variables.
         | 
| 252 | 
            +
             | 
| 253 | 
            +
                    Returns:
         | 
| 254 | 
            +
                        Dictionary with cleanup results
         | 
| 255 | 
            +
                    """
         | 
| 256 | 
            +
                    cleanup_results = {"removed_variables": [], "errors": []}
         | 
| 257 | 
            +
             | 
| 258 | 
            +
                    try:
         | 
| 259 | 
            +
                        # Remove Claude-specific environment variables
         | 
| 260 | 
            +
                        claude_vars = [
         | 
| 261 | 
            +
                            key for key in os.environ.keys() if key.startswith("CLAUDE_")
         | 
| 262 | 
            +
                        ]
         | 
| 263 | 
            +
             | 
| 264 | 
            +
                        for var in claude_vars:
         | 
| 265 | 
            +
                            if var in os.environ:
         | 
| 266 | 
            +
                                del os.environ[var]
         | 
| 267 | 
            +
                                cleanup_results["removed_variables"].append(var)
         | 
| 268 | 
            +
                                self.logger.debug(f"Removed environment variable: {var}")
         | 
| 269 | 
            +
             | 
| 270 | 
            +
                        self.logger.info(
         | 
| 271 | 
            +
                            f"Cleaned up {len(cleanup_results['removed_variables'])} environment variables"
         | 
| 272 | 
            +
                        )
         | 
| 273 | 
            +
             | 
| 274 | 
            +
                    except Exception as e:
         | 
| 275 | 
            +
                        cleanup_results["errors"].append(str(e))
         | 
| 276 | 
            +
                        self.logger.error(f"Error during environment cleanup: {e}")
         | 
| 277 | 
            +
             | 
| 278 | 
            +
                    return cleanup_results
         | 
| 279 | 
            +
             | 
| 280 | 
            +
                def _find_claude_config_locations(self) -> list:
         | 
| 281 | 
            +
                    """Find potential Claude configuration directories."""
         | 
| 282 | 
            +
                    potential_locations = [
         | 
| 283 | 
            +
                        Path.cwd() / ".claude",
         | 
| 284 | 
            +
                        Path.home() / ".claude",
         | 
| 285 | 
            +
                        Path.home() / ".config" / "claude",
         | 
| 286 | 
            +
                    ]
         | 
| 287 | 
            +
             | 
| 288 | 
            +
                    existing_locations = []
         | 
| 289 | 
            +
                    for location in potential_locations:
         | 
| 290 | 
            +
                        if location.exists():
         | 
| 291 | 
            +
                            existing_locations.append(str(location))
         | 
| 292 | 
            +
             | 
| 293 | 
            +
                    return existing_locations
         |