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
| @@ -27,40 +27,50 @@ ROLLBACK PROCEDURES: | |
| 27 27 | 
             
            - Version tracking allows targeted rollbacks
         | 
| 28 28 | 
             
            """
         | 
| 29 29 |  | 
| 30 | 
            +
            import logging
         | 
| 30 31 | 
             
            import os
         | 
| 31 32 | 
             
            import shutil
         | 
| 32 | 
            -
            import logging
         | 
| 33 33 | 
             
            import time
         | 
| 34 34 | 
             
            from pathlib import Path
         | 
| 35 | 
            -
            from typing import  | 
| 35 | 
            +
            from typing import Any, Dict, List, Optional
         | 
| 36 36 |  | 
| 37 | 
            -
            from claude_mpm.core.logging_config import get_logger, log_operation, log_performance_context
         | 
| 38 | 
            -
            from claude_mpm.core.exceptions import AgentDeploymentError
         | 
| 39 | 
            -
            from claude_mpm.constants import EnvironmentVars, Paths, AgentMetadata
         | 
| 40 37 | 
             
            from claude_mpm.config.paths import paths
         | 
| 38 | 
            +
            from claude_mpm.constants import AgentMetadata, EnvironmentVars, Paths
         | 
| 41 39 | 
             
            from claude_mpm.core.config import Config
         | 
| 42 | 
            -
            from claude_mpm.core.constants import  | 
| 43 | 
            -
             | 
| 44 | 
            -
                SystemLimits,
         | 
| 45 | 
            -
                ResourceLimits
         | 
| 46 | 
            -
            )
         | 
| 40 | 
            +
            from claude_mpm.core.constants import ResourceLimits, SystemLimits, TimeoutConfig
         | 
| 41 | 
            +
            from claude_mpm.core.exceptions import AgentDeploymentError
         | 
| 47 42 | 
             
            from claude_mpm.core.interfaces import AgentDeploymentInterface
         | 
| 43 | 
            +
            from claude_mpm.core.logging_config import (
         | 
| 44 | 
            +
                get_logger,
         | 
| 45 | 
            +
                log_operation,
         | 
| 46 | 
            +
                log_performance_context,
         | 
| 47 | 
            +
            )
         | 
| 48 | 
            +
             | 
| 49 | 
            +
            from .agent_configuration_manager import AgentConfigurationManager
         | 
| 50 | 
            +
            from .agent_discovery_service import AgentDiscoveryService
         | 
| 51 | 
            +
            from .agent_environment_manager import AgentEnvironmentManager
         | 
| 52 | 
            +
            from .agent_filesystem_manager import AgentFileSystemManager
         | 
| 53 | 
            +
            from .agent_format_converter import AgentFormatConverter
         | 
| 54 | 
            +
            from .agent_metrics_collector import AgentMetricsCollector
         | 
| 55 | 
            +
            from .agent_template_builder import AgentTemplateBuilder
         | 
| 56 | 
            +
            from .agent_validator import AgentValidator
         | 
| 57 | 
            +
            from .agent_version_manager import AgentVersionManager
         | 
| 48 58 |  | 
| 49 59 |  | 
| 50 60 | 
             
            class AgentDeploymentService(AgentDeploymentInterface):
         | 
| 51 61 | 
             
                """Service for deploying Claude Code native agents.
         | 
| 52 | 
            -
             | 
| 62 | 
            +
             | 
| 53 63 | 
             
                METRICS COLLECTION OPPORTUNITIES:
         | 
| 54 64 | 
             
                This service could collect valuable deployment metrics including:
         | 
| 55 65 | 
             
                - Agent deployment frequency and success rates
         | 
| 56 | 
            -
                - Template validation performance | 
| 66 | 
            +
                - Template validation performance
         | 
| 57 67 | 
             
                - Version migration patterns
         | 
| 58 68 | 
             
                - Deployment duration by agent type
         | 
| 59 69 | 
             
                - Cache hit rates for agent templates
         | 
| 60 70 | 
             
                - Resource usage during deployment (memory, CPU)
         | 
| 61 71 | 
             
                - Agent file sizes and complexity metrics
         | 
| 62 72 | 
             
                - Deployment failure reasons and patterns
         | 
| 63 | 
            -
             | 
| 73 | 
            +
             | 
| 64 74 | 
             
                DEPLOYMENT PIPELINE:
         | 
| 65 75 | 
             
                1. Initialize with template and base agent paths
         | 
| 66 76 | 
             
                2. Load base agent configuration (shared settings)
         | 
| @@ -70,45 +80,64 @@ class AgentDeploymentService(AgentDeploymentInterface): | |
| 70 80 | 
             
                6. Deploy to target directory
         | 
| 71 81 | 
             
                7. Set environment variables for discovery
         | 
| 72 82 | 
             
                8. Verify deployment success
         | 
| 73 | 
            -
             | 
| 83 | 
            +
             | 
| 74 84 | 
             
                ENVIRONMENT REQUIREMENTS:
         | 
| 75 85 | 
             
                - Write access to .claude/agents directory
         | 
| 76 86 | 
             
                - Python 3.8+ for pathlib and typing features
         | 
| 77 87 | 
             
                - JSON parsing for template files
         | 
| 78 88 | 
             
                - YAML generation capabilities
         | 
| 79 89 | 
             
                """
         | 
| 80 | 
            -
             | 
| 81 | 
            -
                def __init__( | 
| 90 | 
            +
             | 
| 91 | 
            +
                def __init__(
         | 
| 92 | 
            +
                    self,
         | 
| 93 | 
            +
                    templates_dir: Optional[Path] = None,
         | 
| 94 | 
            +
                    base_agent_path: Optional[Path] = None,
         | 
| 95 | 
            +
                    working_directory: Optional[Path] = None,
         | 
| 96 | 
            +
                ):
         | 
| 82 97 | 
             
                    """
         | 
| 83 98 | 
             
                    Initialize agent deployment service.
         | 
| 84 | 
            -
             | 
| 99 | 
            +
             | 
| 85 100 | 
             
                    Args:
         | 
| 86 101 | 
             
                        templates_dir: Directory containing agent JSON files
         | 
| 87 102 | 
             
                        base_agent_path: Path to base_agent.md file
         | 
| 88 103 | 
             
                        working_directory: User's working directory (for project agents)
         | 
| 89 | 
            -
             | 
| 104 | 
            +
             | 
| 90 105 | 
             
                    METRICS OPPORTUNITY: Track initialization performance:
         | 
| 91 106 | 
             
                    - Template directory scan time
         | 
| 92 107 | 
             
                    - Base agent loading time
         | 
| 93 108 | 
             
                    - Initial validation overhead
         | 
| 94 109 | 
             
                    """
         | 
| 95 110 | 
             
                    self.logger = get_logger(__name__)
         | 
| 96 | 
            -
             | 
| 97 | 
            -
                    #  | 
| 98 | 
            -
                     | 
| 111 | 
            +
             | 
| 112 | 
            +
                    # Initialize template builder service
         | 
| 113 | 
            +
                    self.template_builder = AgentTemplateBuilder()
         | 
| 114 | 
            +
             | 
| 115 | 
            +
                    # Initialize version manager service
         | 
| 116 | 
            +
                    self.version_manager = AgentVersionManager()
         | 
| 117 | 
            +
             | 
| 118 | 
            +
                    # Initialize metrics collector service
         | 
| 119 | 
            +
                    self.metrics_collector = AgentMetricsCollector()
         | 
| 120 | 
            +
             | 
| 121 | 
            +
                    # Initialize environment manager service
         | 
| 122 | 
            +
                    self.environment_manager = AgentEnvironmentManager()
         | 
| 123 | 
            +
             | 
| 124 | 
            +
                    # Initialize validator service
         | 
| 125 | 
            +
                    self.validator = AgentValidator()
         | 
| 126 | 
            +
             | 
| 127 | 
            +
                    # Initialize filesystem manager service
         | 
| 128 | 
            +
                    self.filesystem_manager = AgentFileSystemManager()
         | 
| 129 | 
            +
             | 
| 130 | 
            +
                    # Initialize deployment metrics tracking
         | 
| 99 131 | 
             
                    self._deployment_metrics = {
         | 
| 100 | 
            -
                         | 
| 101 | 
            -
                         | 
| 102 | 
            -
                         | 
| 103 | 
            -
                         | 
| 104 | 
            -
                         | 
| 105 | 
            -
                         | 
| 106 | 
            -
                         | 
| 107 | 
            -
                        'version_migration_count': 0,
         | 
| 108 | 
            -
                        'template_validation_times': {},  # Track validation performance
         | 
| 109 | 
            -
                        'deployment_errors': {}  # Track error types and frequencies
         | 
| 132 | 
            +
                        "total_deployments": 0,
         | 
| 133 | 
            +
                        "successful_deployments": 0,
         | 
| 134 | 
            +
                        "failed_deployments": 0,
         | 
| 135 | 
            +
                        "migrations_performed": 0,
         | 
| 136 | 
            +
                        "version_migration_count": 0,
         | 
| 137 | 
            +
                        "agent_type_counts": {},
         | 
| 138 | 
            +
                        "deployment_errors": {},
         | 
| 110 139 | 
             
                    }
         | 
| 111 | 
            -
             | 
| 140 | 
            +
             | 
| 112 141 | 
             
                    # Determine the actual working directory
         | 
| 113 142 | 
             
                    # For deployment, we need to track the working directory but NOT use it
         | 
| 114 143 | 
             
                    # for determining where system agents go (they always go to ~/.claude/agents/)
         | 
| @@ -118,9 +147,9 @@ class AgentDeploymentService(AgentDeploymentInterface): | |
| 118 147 | 
             
                    else:
         | 
| 119 148 | 
             
                        # Use current directory but don't let CLAUDE_MPM_USER_PWD affect system agent deployment
         | 
| 120 149 | 
             
                        self.working_directory = Path.cwd()
         | 
| 121 | 
            -
             | 
| 150 | 
            +
             | 
| 122 151 | 
             
                    self.logger.info(f"Working directory for deployment: {self.working_directory}")
         | 
| 123 | 
            -
             | 
| 152 | 
            +
             | 
| 124 153 | 
             
                    # Find templates directory using centralized path management
         | 
| 125 154 | 
             
                    if templates_dir:
         | 
| 126 155 | 
             
                        self.templates_dir = Path(templates_dir)
         | 
| @@ -129,32 +158,48 @@ class AgentDeploymentService(AgentDeploymentInterface): | |
| 129 158 | 
             
                        # For system agents, still use templates subdirectory
         | 
| 130 159 | 
             
                        # For project/user agents, this should be overridden with actual agents dir
         | 
| 131 160 | 
             
                        self.templates_dir = paths.agents_dir / "templates"
         | 
| 132 | 
            -
             | 
| 161 | 
            +
             | 
| 162 | 
            +
                    # Initialize discovery service (after templates_dir is set)
         | 
| 163 | 
            +
                    self.discovery_service = AgentDiscoveryService(self.templates_dir)
         | 
| 164 | 
            +
             | 
| 133 165 | 
             
                    # Find base agent file
         | 
| 134 166 | 
             
                    if base_agent_path:
         | 
| 135 167 | 
             
                        self.base_agent_path = Path(base_agent_path)
         | 
| 136 168 | 
             
                    else:
         | 
| 137 169 | 
             
                        # Use centralized paths for consistency
         | 
| 138 170 | 
             
                        self.base_agent_path = paths.agents_dir / "base_agent.json"
         | 
| 139 | 
            -
             | 
| 171 | 
            +
             | 
| 172 | 
            +
                    # Initialize configuration manager (after base_agent_path is set)
         | 
| 173 | 
            +
                    self.configuration_manager = AgentConfigurationManager(self.base_agent_path)
         | 
| 174 | 
            +
             | 
| 175 | 
            +
                    # Initialize format converter service
         | 
| 176 | 
            +
                    self.format_converter = AgentFormatConverter()
         | 
| 177 | 
            +
             | 
| 140 178 | 
             
                    self.logger.info(f"Templates directory: {self.templates_dir}")
         | 
| 141 179 | 
             
                    self.logger.info(f"Base agent path: {self.base_agent_path}")
         | 
| 142 | 
            -
             | 
| 143 | 
            -
                def deploy_agents( | 
| 180 | 
            +
             | 
| 181 | 
            +
                def deploy_agents(
         | 
| 182 | 
            +
                    self,
         | 
| 183 | 
            +
                    target_dir: Optional[Path] = None,
         | 
| 184 | 
            +
                    force_rebuild: bool = False,
         | 
| 185 | 
            +
                    deployment_mode: str = "update",
         | 
| 186 | 
            +
                    config: Optional[Config] = None,
         | 
| 187 | 
            +
                    use_async: bool = False,
         | 
| 188 | 
            +
                ) -> Dict[str, Any]:
         | 
| 144 189 | 
             
                    """
         | 
| 145 190 | 
             
                    Build and deploy agents by combining base_agent.md with templates.
         | 
| 146 191 | 
             
                    Also deploys system instructions for PM framework.
         | 
| 147 | 
            -
             | 
| 192 | 
            +
             | 
| 148 193 | 
             
                    DEPLOYMENT MODES:
         | 
| 149 194 | 
             
                    - "update": Normal update mode - skip agents with matching versions (default)
         | 
| 150 195 | 
             
                    - "project": Project deployment mode - always deploy all agents regardless of version
         | 
| 151 | 
            -
             | 
| 196 | 
            +
             | 
| 152 197 | 
             
                    CONFIGURATION:
         | 
| 153 198 | 
             
                    The config parameter or default configuration is used to determine:
         | 
| 154 199 | 
             
                    - Which agents to exclude from deployment
         | 
| 155 200 | 
             
                    - Case sensitivity for agent name matching
         | 
| 156 201 | 
             
                    - Whether to exclude agent dependencies
         | 
| 157 | 
            -
             | 
| 202 | 
            +
             | 
| 158 203 | 
             
                    METRICS COLLECTED:
         | 
| 159 204 | 
             
                    - Deployment start/end timestamps
         | 
| 160 205 | 
             
                    - Individual agent deployment durations
         | 
| @@ -162,7 +207,7 @@ class AgentDeploymentService(AgentDeploymentInterface): | |
| 162 207 | 
             
                    - Version migration statistics
         | 
| 163 208 | 
             
                    - Template validation performance
         | 
| 164 209 | 
             
                    - Error type frequencies
         | 
| 165 | 
            -
             | 
| 210 | 
            +
             | 
| 166 211 | 
             
                    OPERATIONAL FLOW:
         | 
| 167 212 | 
             
                    0. Validates and repairs broken frontmatter in existing agents (Step 0)
         | 
| 168 213 | 
             
                    1. Validates target directory (creates if needed)
         | 
| @@ -173,32 +218,32 @@ class AgentDeploymentService(AgentDeploymentInterface): | |
| 173 218 | 
             
                       - Builds YAML configuration
         | 
| 174 219 | 
             
                       - Writes to target directory
         | 
| 175 220 | 
             
                       - Tracks deployment status
         | 
| 176 | 
            -
             | 
| 221 | 
            +
             | 
| 177 222 | 
             
                    PERFORMANCE CONSIDERATIONS:
         | 
| 178 223 | 
             
                    - Skips unchanged agents (version-based caching)
         | 
| 179 224 | 
             
                    - Batch processes all agents in single pass
         | 
| 180 225 | 
             
                    - Minimal file I/O with in-memory building
         | 
| 181 226 | 
             
                    - Parallel-safe (no shared state mutations)
         | 
| 182 | 
            -
             | 
| 227 | 
            +
             | 
| 183 228 | 
             
                    ERROR HANDLING:
         | 
| 184 229 | 
             
                    - Continues deployment on individual agent failures
         | 
| 185 230 | 
             
                    - Collects all errors for reporting
         | 
| 186 231 | 
             
                    - Logs detailed error context
         | 
| 187 232 | 
             
                    - Returns comprehensive results dict
         | 
| 188 | 
            -
             | 
| 233 | 
            +
             | 
| 189 234 | 
             
                    MONITORING POINTS:
         | 
| 190 235 | 
             
                    - Track total deployment time
         | 
| 191 236 | 
             
                    - Monitor skipped vs updated vs new agents
         | 
| 192 237 | 
             
                    - Check error rates and patterns
         | 
| 193 238 | 
             
                    - Verify migration completion
         | 
| 194 | 
            -
             | 
| 239 | 
            +
             | 
| 195 240 | 
             
                    Args:
         | 
| 196 241 | 
             
                        target_dir: Target directory for agents (default: .claude/agents/)
         | 
| 197 242 | 
             
                        force_rebuild: Force rebuild even if agents exist (useful for troubleshooting)
         | 
| 198 243 | 
             
                        deployment_mode: "update" for version-aware updates, "project" for always deploy
         | 
| 199 244 | 
             
                        config: Optional configuration object (loads default if not provided)
         | 
| 200 245 | 
             
                        use_async: Use async operations for 50-70% faster deployment (default: True)
         | 
| 201 | 
            -
             | 
| 246 | 
            +
             | 
| 202 247 | 
             
                    Returns:
         | 
| 203 248 | 
             
                        Dictionary with deployment results:
         | 
| 204 249 | 
             
                        - target_dir: Deployment location
         | 
| @@ -212,61 +257,63 @@ class AgentDeploymentService(AgentDeploymentInterface): | |
| 212 257 | 
             
                    """
         | 
| 213 258 | 
             
                    # METRICS: Record deployment start time for performance tracking
         | 
| 214 259 | 
             
                    deployment_start_time = time.time()
         | 
| 215 | 
            -
             | 
| 260 | 
            +
             | 
| 216 261 | 
             
                    # Try async deployment for better performance if requested
         | 
| 217 262 | 
             
                    if use_async:
         | 
| 218 263 | 
             
                        async_results = self._try_async_deployment(
         | 
| 219 264 | 
             
                            target_dir=target_dir,
         | 
| 220 265 | 
             
                            force_rebuild=force_rebuild,
         | 
| 221 266 | 
             
                            config=config,
         | 
| 222 | 
            -
                            deployment_start_time=deployment_start_time
         | 
| 267 | 
            +
                            deployment_start_time=deployment_start_time,
         | 
| 223 268 | 
             
                        )
         | 
| 224 269 | 
             
                        if async_results is not None:
         | 
| 225 270 | 
             
                            return async_results
         | 
| 226 | 
            -
             | 
| 271 | 
            +
             | 
| 227 272 | 
             
                    # Continue with synchronous deployment
         | 
| 228 273 | 
             
                    self.logger.info("Using synchronous deployment")
         | 
| 229 | 
            -
             | 
| 274 | 
            +
             | 
| 230 275 | 
             
                    # Load and process configuration
         | 
| 231 276 | 
             
                    config, excluded_agents = self._load_deployment_config(config)
         | 
| 232 | 
            -
             | 
| 277 | 
            +
             | 
| 233 278 | 
             
                    # Determine target agents directory
         | 
| 234 279 | 
             
                    agents_dir = self._determine_agents_directory(target_dir)
         | 
| 235 | 
            -
             | 
| 280 | 
            +
             | 
| 236 281 | 
             
                    # Initialize results dictionary
         | 
| 237 282 | 
             
                    results = self._initialize_deployment_results(agents_dir, deployment_start_time)
         | 
| 238 | 
            -
             | 
| 283 | 
            +
             | 
| 239 284 | 
             
                    try:
         | 
| 240 285 | 
             
                        # Create agents directory if needed
         | 
| 241 286 | 
             
                        agents_dir.mkdir(parents=True, exist_ok=True)
         | 
| 242 | 
            -
             | 
| 287 | 
            +
             | 
| 243 288 | 
             
                        # STEP 0: Validate and repair broken frontmatter in existing agents
         | 
| 244 289 | 
             
                        self._repair_existing_agents(agents_dir, results)
         | 
| 245 | 
            -
             | 
| 290 | 
            +
             | 
| 246 291 | 
             
                        # Log deployment source tier
         | 
| 247 292 | 
             
                        source_tier = self._determine_source_tier()
         | 
| 248 | 
            -
                        self.logger.info( | 
| 249 | 
            -
             | 
| 293 | 
            +
                        self.logger.info(
         | 
| 294 | 
            +
                            f"Building and deploying {source_tier} agents to: {agents_dir}"
         | 
| 295 | 
            +
                        )
         | 
| 296 | 
            +
             | 
| 250 297 | 
             
                        # Note: System instructions are now loaded directly by SimpleClaudeRunner
         | 
| 251 | 
            -
             | 
| 298 | 
            +
             | 
| 252 299 | 
             
                        # Check if templates directory exists
         | 
| 253 300 | 
             
                        if not self.templates_dir.exists():
         | 
| 254 301 | 
             
                            error_msg = f"Agents directory not found: {self.templates_dir}"
         | 
| 255 302 | 
             
                            self.logger.error(error_msg)
         | 
| 256 303 | 
             
                            results["errors"].append(error_msg)
         | 
| 257 304 | 
             
                            return results
         | 
| 258 | 
            -
             | 
| 305 | 
            +
             | 
| 259 306 | 
             
                        # Convert any existing YAML files to MD format
         | 
| 260 307 | 
             
                        conversion_results = self._convert_yaml_to_md(agents_dir)
         | 
| 261 308 | 
             
                        results["converted"] = conversion_results.get("converted", [])
         | 
| 262 | 
            -
             | 
| 309 | 
            +
             | 
| 263 310 | 
             
                        # Load base agent content
         | 
| 264 311 | 
             
                        base_agent_data, base_agent_version = self._load_base_agent()
         | 
| 265 | 
            -
             | 
| 312 | 
            +
             | 
| 266 313 | 
             
                        # Get and filter template files
         | 
| 267 314 | 
             
                        template_files = self._get_filtered_templates(excluded_agents, config)
         | 
| 268 315 | 
             
                        results["total"] = len(template_files)
         | 
| 269 | 
            -
             | 
| 316 | 
            +
             | 
| 270 317 | 
             
                        # Deploy each agent template
         | 
| 271 318 | 
             
                        for template_file in template_files:
         | 
| 272 319 | 
             
                            self._deploy_single_agent(
         | 
| @@ -276,12 +323,12 @@ class AgentDeploymentService(AgentDeploymentInterface): | |
| 276 323 | 
             
                                base_agent_version=base_agent_version,
         | 
| 277 324 | 
             
                                force_rebuild=force_rebuild,
         | 
| 278 325 | 
             
                                deployment_mode=deployment_mode,
         | 
| 279 | 
            -
                                results=results
         | 
| 326 | 
            +
                                results=results,
         | 
| 280 327 | 
             
                            )
         | 
| 281 | 
            -
             | 
| 328 | 
            +
             | 
| 282 329 | 
             
                        # Deploy system instructions and framework files
         | 
| 283 330 | 
             
                        self._deploy_system_instructions(agents_dir, force_rebuild, results)
         | 
| 284 | 
            -
             | 
| 331 | 
            +
             | 
| 285 332 | 
             
                        self.logger.info(
         | 
| 286 333 | 
             
                            f"Deployed {len(results['deployed'])} agents, "
         | 
| 287 334 | 
             
                            f"updated {len(results['updated'])}, "
         | 
| @@ -291,7 +338,7 @@ class AgentDeploymentService(AgentDeploymentInterface): | |
| 291 338 | 
             
                            f"skipped {len(results['skipped'])}, "
         | 
| 292 339 | 
             
                            f"errors: {len(results['errors'])}"
         | 
| 293 340 | 
             
                        )
         | 
| 294 | 
            -
             | 
| 341 | 
            +
             | 
| 295 342 | 
             
                    except AgentDeploymentError as e:
         | 
| 296 343 | 
             
                        # Custom error with context already formatted
         | 
| 297 344 | 
             
                        self.logger.error(str(e))
         | 
| @@ -301,679 +348,67 @@ class AgentDeploymentService(AgentDeploymentInterface): | |
| 301 348 | 
             
                        error_msg = f"Agent deployment failed: {e}"
         | 
| 302 349 | 
             
                        self.logger.error(error_msg)
         | 
| 303 350 | 
             
                        results["errors"].append(error_msg)
         | 
| 304 | 
            -
             | 
| 351 | 
            +
             | 
| 305 352 | 
             
                        # METRICS: Track deployment failure
         | 
| 306 | 
            -
                        self._deployment_metrics[ | 
| 353 | 
            +
                        self._deployment_metrics["failed_deployments"] += 1
         | 
| 307 354 | 
             
                        error_type = type(e).__name__
         | 
| 308 | 
            -
                        self._deployment_metrics[ | 
| 309 | 
            -
                            self._deployment_metrics[ | 
| 310 | 
            -
             | 
| 355 | 
            +
                        self._deployment_metrics["deployment_errors"][error_type] = (
         | 
| 356 | 
            +
                            self._deployment_metrics["deployment_errors"].get(error_type, 0) + 1
         | 
| 357 | 
            +
                        )
         | 
| 358 | 
            +
             | 
| 311 359 | 
             
                    # METRICS: Calculate final deployment metrics
         | 
| 312 360 | 
             
                    deployment_end_time = time.time()
         | 
| 313 361 | 
             
                    deployment_duration = (deployment_end_time - deployment_start_time) * 1000  # ms
         | 
| 314 | 
            -
             | 
| 362 | 
            +
             | 
| 315 363 | 
             
                    results["metrics"]["end_time"] = deployment_end_time
         | 
| 316 364 | 
             
                    results["metrics"]["duration_ms"] = deployment_duration
         | 
| 317 | 
            -
             | 
| 365 | 
            +
             | 
| 318 366 | 
             
                    # METRICS: Update rolling averages and statistics
         | 
| 319 | 
            -
                    self. | 
| 320 | 
            -
             | 
| 367 | 
            +
                    self.metrics_collector.update_deployment_metrics(deployment_duration, results)
         | 
| 368 | 
            +
             | 
| 321 369 | 
             
                    return results
         | 
| 322 | 
            -
             | 
| 323 | 
            -
                def _update_deployment_metrics(self, duration_ms: float, results: Dict[str, Any]) -> None:
         | 
| 324 | 
            -
                    """
         | 
| 325 | 
            -
                    Update internal deployment metrics.
         | 
| 326 | 
            -
                    
         | 
| 327 | 
            -
                    METRICS TRACKING:
         | 
| 328 | 
            -
                    - Rolling average of deployment times (last 100)
         | 
| 329 | 
            -
                    - Success/failure rates
         | 
| 330 | 
            -
                    - Agent type distribution
         | 
| 331 | 
            -
                    - Version migration patterns
         | 
| 332 | 
            -
                    - Error frequency analysis
         | 
| 333 | 
            -
                    
         | 
| 334 | 
            -
                    This method demonstrates ETL-like processing:
         | 
| 335 | 
            -
                    1. Extract: Gather raw metrics from deployment results
         | 
| 336 | 
            -
                    2. Transform: Calculate averages, rates, and distributions
         | 
| 337 | 
            -
                    3. Load: Store in internal metrics structure for reporting
         | 
| 338 | 
            -
                    """
         | 
| 339 | 
            -
                    # Update total deployment count
         | 
| 340 | 
            -
                    self._deployment_metrics['total_deployments'] += 1
         | 
| 341 | 
            -
                    
         | 
| 342 | 
            -
                    # Track success/failure
         | 
| 343 | 
            -
                    if not results.get('errors'):
         | 
| 344 | 
            -
                        self._deployment_metrics['successful_deployments'] += 1
         | 
| 345 | 
            -
                    else:
         | 
| 346 | 
            -
                        self._deployment_metrics['failed_deployments'] += 1
         | 
| 347 | 
            -
                    
         | 
| 348 | 
            -
                    # Update rolling average deployment time
         | 
| 349 | 
            -
                    self._deployment_metrics['deployment_times'].append(duration_ms)
         | 
| 350 | 
            -
                    if len(self._deployment_metrics['deployment_times']) > 100:
         | 
| 351 | 
            -
                        # Keep only last 100 for memory efficiency
         | 
| 352 | 
            -
                        self._deployment_metrics['deployment_times'] = \
         | 
| 353 | 
            -
                            self._deployment_metrics['deployment_times'][-100:]
         | 
| 354 | 
            -
                    
         | 
| 355 | 
            -
                    # Calculate new average
         | 
| 356 | 
            -
                    if self._deployment_metrics['deployment_times']:
         | 
| 357 | 
            -
                        self._deployment_metrics['average_deployment_time_ms'] = \
         | 
| 358 | 
            -
                            sum(self._deployment_metrics['deployment_times']) / \
         | 
| 359 | 
            -
                            len(self._deployment_metrics['deployment_times'])
         | 
| 360 | 
            -
                
         | 
| 370 | 
            +
             | 
| 361 371 | 
             
                def get_deployment_metrics(self) -> Dict[str, Any]:
         | 
| 362 | 
            -
                    """
         | 
| 363 | 
            -
                     | 
| 364 | 
            -
             | 
| 365 | 
            -
                    Returns:
         | 
| 366 | 
            -
                        Dictionary containing:
         | 
| 367 | 
            -
                        - Total deployments and success rates
         | 
| 368 | 
            -
                        - Average deployment time
         | 
| 369 | 
            -
                        - Agent type distribution
         | 
| 370 | 
            -
                        - Migration statistics
         | 
| 371 | 
            -
                        - Error analysis
         | 
| 372 | 
            -
                        
         | 
| 373 | 
            -
                    This demonstrates a metrics API endpoint that could be:
         | 
| 374 | 
            -
                    - Exposed via REST API for monitoring tools
         | 
| 375 | 
            -
                    - Pushed to time-series databases (Prometheus, InfluxDB)
         | 
| 376 | 
            -
                    - Used for dashboards and alerting
         | 
| 377 | 
            -
                    - Integrated with AI observability platforms
         | 
| 378 | 
            -
                    """
         | 
| 379 | 
            -
                    success_rate = 0.0
         | 
| 380 | 
            -
                    if self._deployment_metrics['total_deployments'] > 0:
         | 
| 381 | 
            -
                        success_rate = (self._deployment_metrics['successful_deployments'] / 
         | 
| 382 | 
            -
                                      self._deployment_metrics['total_deployments']) * 100
         | 
| 383 | 
            -
                    
         | 
| 384 | 
            -
                    return {
         | 
| 385 | 
            -
                        'total_deployments': self._deployment_metrics['total_deployments'],
         | 
| 386 | 
            -
                        'successful_deployments': self._deployment_metrics['successful_deployments'],
         | 
| 387 | 
            -
                        'failed_deployments': self._deployment_metrics['failed_deployments'],
         | 
| 388 | 
            -
                        'success_rate_percent': success_rate,
         | 
| 389 | 
            -
                        'average_deployment_time_ms': self._deployment_metrics['average_deployment_time_ms'],
         | 
| 390 | 
            -
                        'migrations_performed': self._deployment_metrics['migrations_performed'],
         | 
| 391 | 
            -
                        'agent_type_distribution': self._deployment_metrics['agent_type_counts'].copy(),
         | 
| 392 | 
            -
                        'version_migrations': self._deployment_metrics['version_migration_count'],
         | 
| 393 | 
            -
                        'error_distribution': self._deployment_metrics['deployment_errors'].copy(),
         | 
| 394 | 
            -
                        'recent_deployment_times': self._deployment_metrics['deployment_times'][-10:]  # Last 10
         | 
| 395 | 
            -
                    }
         | 
| 396 | 
            -
                
         | 
| 372 | 
            +
                    """Get current deployment metrics."""
         | 
| 373 | 
            +
                    return self.metrics_collector.get_deployment_metrics()
         | 
| 374 | 
            +
             | 
| 397 375 | 
             
                def reset_metrics(self) -> None:
         | 
| 398 | 
            -
                    """
         | 
| 399 | 
            -
                     | 
| 400 | 
            -
                    
         | 
| 401 | 
            -
                    Useful for:
         | 
| 402 | 
            -
                    - Starting fresh metrics collection periods
         | 
| 403 | 
            -
                    - Testing and development
         | 
| 404 | 
            -
                    - Scheduled metric rotation (e.g., daily reset)
         | 
| 405 | 
            -
                    """
         | 
| 406 | 
            -
                    self._deployment_metrics = {
         | 
| 407 | 
            -
                        'total_deployments': 0,
         | 
| 408 | 
            -
                        'successful_deployments': 0,
         | 
| 409 | 
            -
                        'failed_deployments': 0,
         | 
| 410 | 
            -
                        'migrations_performed': 0,
         | 
| 411 | 
            -
                        'average_deployment_time_ms': 0.0,
         | 
| 412 | 
            -
                        'deployment_times': [],
         | 
| 413 | 
            -
                        'agent_type_counts': {},
         | 
| 414 | 
            -
                        'version_migration_count': 0,
         | 
| 415 | 
            -
                        'template_validation_times': {},
         | 
| 416 | 
            -
                        'deployment_errors': {}
         | 
| 417 | 
            -
                    }
         | 
| 418 | 
            -
                    self.logger.info("Deployment metrics reset")
         | 
| 419 | 
            -
                
         | 
| 420 | 
            -
                def _extract_version(self, content: str, version_marker: str) -> int:
         | 
| 421 | 
            -
                    """
         | 
| 422 | 
            -
                    Extract version number from content.
         | 
| 423 | 
            -
                    
         | 
| 424 | 
            -
                    Args:
         | 
| 425 | 
            -
                        content: File content
         | 
| 426 | 
            -
                        version_marker: Version marker to look for (e.g., "AGENT_VERSION:" or "BASE_AGENT_VERSION:")
         | 
| 427 | 
            -
                        
         | 
| 428 | 
            -
                    Returns:
         | 
| 429 | 
            -
                        Version number or 0 if not found
         | 
| 430 | 
            -
                    """
         | 
| 431 | 
            -
                    import re
         | 
| 432 | 
            -
                    pattern = rf"<!-- {version_marker} (\d+) -->"
         | 
| 433 | 
            -
                    match = re.search(pattern, content)
         | 
| 434 | 
            -
                    if match:
         | 
| 435 | 
            -
                        return int(match.group(1))
         | 
| 436 | 
            -
                    return 0
         | 
| 437 | 
            -
                
         | 
| 438 | 
            -
                def _build_agent_markdown(self, agent_name: str, template_path: Path, base_agent_data: dict) -> str:
         | 
| 439 | 
            -
                    """
         | 
| 440 | 
            -
                    Build a complete agent markdown file with YAML frontmatter.
         | 
| 441 | 
            -
                    
         | 
| 442 | 
            -
                    Args:
         | 
| 443 | 
            -
                        agent_name: Name of the agent
         | 
| 444 | 
            -
                        template_path: Path to the agent template JSON file
         | 
| 445 | 
            -
                        base_agent_data: Base agent data from JSON
         | 
| 446 | 
            -
                        
         | 
| 447 | 
            -
                    Returns:
         | 
| 448 | 
            -
                        Complete agent markdown content with YAML frontmatter
         | 
| 449 | 
            -
                    """
         | 
| 450 | 
            -
                    import json
         | 
| 451 | 
            -
                    from datetime import datetime
         | 
| 452 | 
            -
                    
         | 
| 453 | 
            -
                    # Read template JSON
         | 
| 454 | 
            -
                    template_data = json.loads(template_path.read_text())
         | 
| 455 | 
            -
                    
         | 
| 456 | 
            -
                    # Extract basic info
         | 
| 457 | 
            -
                    # Handle both 'agent_version' (new format) and 'version' (old format)
         | 
| 458 | 
            -
                    agent_version = self._parse_version(template_data.get('agent_version') or template_data.get('version', 0))
         | 
| 459 | 
            -
                    base_version = self._parse_version(base_agent_data.get('base_version') or base_agent_data.get('version', 0))
         | 
| 460 | 
            -
                    
         | 
| 461 | 
            -
                    # Format version string as semantic version
         | 
| 462 | 
            -
                    # Combine base and agent versions for a unified semantic version
         | 
| 463 | 
            -
                    # Use agent version as primary, with base version in metadata
         | 
| 464 | 
            -
                    version_string = self._format_version_display(agent_version)
         | 
| 465 | 
            -
                    
         | 
| 466 | 
            -
                    # Build YAML frontmatter
         | 
| 467 | 
            -
                    # Check new format first (metadata.description), then old format
         | 
| 468 | 
            -
                    description = (
         | 
| 469 | 
            -
                        template_data.get('metadata', {}).get('description') or
         | 
| 470 | 
            -
                        template_data.get('configuration_fields', {}).get('description') or
         | 
| 471 | 
            -
                        template_data.get('description') or
         | 
| 472 | 
            -
                        'Agent for specialized tasks'
         | 
| 473 | 
            -
                    )
         | 
| 474 | 
            -
                    
         | 
| 475 | 
            -
                    # Get tags from new format (metadata.tags) or old format
         | 
| 476 | 
            -
                    tags = (
         | 
| 477 | 
            -
                        template_data.get('metadata', {}).get('tags') or
         | 
| 478 | 
            -
                        template_data.get('configuration_fields', {}).get('tags') or
         | 
| 479 | 
            -
                        template_data.get('tags') or
         | 
| 480 | 
            -
                        [agent_name, 'mpm-framework']
         | 
| 481 | 
            -
                    )
         | 
| 482 | 
            -
                    
         | 
| 483 | 
            -
                    # Get tools from capabilities.tools in new format
         | 
| 484 | 
            -
                    tools = (
         | 
| 485 | 
            -
                        template_data.get('capabilities', {}).get('tools') or
         | 
| 486 | 
            -
                        template_data.get('configuration_fields', {}).get('tools') or
         | 
| 487 | 
            -
                        ["Read", "Write", "Edit", "Grep", "Glob", "LS"]  # Default fallback
         | 
| 488 | 
            -
                    )
         | 
| 489 | 
            -
                    
         | 
| 490 | 
            -
                    # Get model from capabilities.model in new format
         | 
| 491 | 
            -
                    model = (
         | 
| 492 | 
            -
                        template_data.get('capabilities', {}).get('model') or
         | 
| 493 | 
            -
                        template_data.get('configuration_fields', {}).get('model') or
         | 
| 494 | 
            -
                        "sonnet"  # Default fallback
         | 
| 495 | 
            -
                    )
         | 
| 496 | 
            -
                    
         | 
| 497 | 
            -
                    # Simplify model name for Claude Code
         | 
| 498 | 
            -
                    model_map = {
         | 
| 499 | 
            -
                        'claude-4-sonnet-20250514': 'sonnet',
         | 
| 500 | 
            -
                        'claude-sonnet-4-20250514': 'sonnet',
         | 
| 501 | 
            -
                        'claude-opus-4-20250514': 'opus',
         | 
| 502 | 
            -
                        'claude-3-opus-20240229': 'opus',
         | 
| 503 | 
            -
                        'claude-3-haiku-20240307': 'haiku',
         | 
| 504 | 
            -
                        'claude-3.5-sonnet': 'sonnet',
         | 
| 505 | 
            -
                        'claude-3-sonnet': 'sonnet'
         | 
| 506 | 
            -
                    }
         | 
| 507 | 
            -
                    # Better fallback: extract the model type (opus/sonnet/haiku) from the string
         | 
| 508 | 
            -
                    if model not in model_map:
         | 
| 509 | 
            -
                        if 'opus' in model.lower():
         | 
| 510 | 
            -
                            model = 'opus'
         | 
| 511 | 
            -
                        elif 'sonnet' in model.lower():
         | 
| 512 | 
            -
                            model = 'sonnet'
         | 
| 513 | 
            -
                        elif 'haiku' in model.lower():
         | 
| 514 | 
            -
                            model = 'haiku'
         | 
| 515 | 
            -
                        else:
         | 
| 516 | 
            -
                            # Last resort: try to extract from hyphenated format
         | 
| 517 | 
            -
                            model = model_map.get(model, model.split('-')[-1] if '-' in model else model)
         | 
| 518 | 
            -
                    else:
         | 
| 519 | 
            -
                        model = model_map[model]
         | 
| 520 | 
            -
                    
         | 
| 521 | 
            -
                    # Get response format from template or use base agent default
         | 
| 522 | 
            -
                    response_format = template_data.get('response', {}).get('format', 'structured')
         | 
| 523 | 
            -
                    
         | 
| 524 | 
            -
                    # Convert lists to space-separated strings for Claude Code compatibility
         | 
| 525 | 
            -
                    tags_str = ' '.join(tags) if isinstance(tags, list) else tags
         | 
| 526 | 
            -
                    
         | 
| 527 | 
            -
                    # Convert tools list to comma-separated string for Claude Code compatibility
         | 
| 528 | 
            -
                    # IMPORTANT: No spaces after commas - Claude Code requires exact format
         | 
| 529 | 
            -
                    tools_str = ','.join(tools) if isinstance(tools, list) else tools
         | 
| 530 | 
            -
                    
         | 
| 531 | 
            -
                    # Validate tools format - CRITICAL: No spaces allowed!
         | 
| 532 | 
            -
                    if ', ' in tools_str:
         | 
| 533 | 
            -
                        self.logger.error(f"Tools contain spaces: '{tools_str}'")
         | 
| 534 | 
            -
                        raise AgentDeploymentError(
         | 
| 535 | 
            -
                            f"Tools must be comma-separated WITHOUT spaces",
         | 
| 536 | 
            -
                            context={"agent_name": agent_name, "invalid_tools": tools_str}
         | 
| 537 | 
            -
                        )
         | 
| 538 | 
            -
                    
         | 
| 539 | 
            -
                    # Extract proper agent_id and name from template
         | 
| 540 | 
            -
                    agent_id = template_data.get('agent_id', agent_name)
         | 
| 541 | 
            -
                    display_name = template_data.get('metadata', {}).get('name', agent_id)
         | 
| 542 | 
            -
                    
         | 
| 543 | 
            -
                    # Convert agent_id to Claude Code compatible name (replace underscores with hyphens)
         | 
| 544 | 
            -
                    # Claude Code requires name to match pattern: ^[a-z0-9]+(-[a-z0-9]+)*$
         | 
| 545 | 
            -
                    # CRITICAL: NO underscores allowed - they cause silent failures!
         | 
| 546 | 
            -
                    claude_code_name = agent_id.replace('_', '-').lower()
         | 
| 547 | 
            -
                    
         | 
| 548 | 
            -
                    # Validate the name before proceeding
         | 
| 549 | 
            -
                    import re
         | 
| 550 | 
            -
                    if not re.match(r'^[a-z0-9]+(-[a-z0-9]+)*$', claude_code_name):
         | 
| 551 | 
            -
                        self.logger.error(f"Invalid agent name '{claude_code_name}' - must match ^[a-z0-9]+(-[a-z0-9]+)*$")
         | 
| 552 | 
            -
                        raise AgentDeploymentError(
         | 
| 553 | 
            -
                            f"Agent name '{claude_code_name}' does not meet Claude Code requirements",
         | 
| 554 | 
            -
                            context={"agent_name": agent_name, "invalid_name": claude_code_name}
         | 
| 555 | 
            -
                        )
         | 
| 556 | 
            -
                    
         | 
| 557 | 
            -
                    # Build frontmatter with only the fields Claude Code uses
         | 
| 558 | 
            -
                    frontmatter_lines = [
         | 
| 559 | 
            -
                        "---",
         | 
| 560 | 
            -
                        f"name: {claude_code_name}",
         | 
| 561 | 
            -
                        f"description: {description}",
         | 
| 562 | 
            -
                        f"version: {version_string}",
         | 
| 563 | 
            -
                        f"base_version: {self._format_version_display(base_version)}",
         | 
| 564 | 
            -
                        f"author: claude-mpm",  # Identify as system agent for deployment
         | 
| 565 | 
            -
                        f"tools: {tools_str}",
         | 
| 566 | 
            -
                        f"model: {model}"
         | 
| 567 | 
            -
                    ]
         | 
| 568 | 
            -
                    
         | 
| 569 | 
            -
                    # Add optional fields if present
         | 
| 570 | 
            -
                    # Check for color in metadata section (new format) or root (old format)
         | 
| 571 | 
            -
                    color = (
         | 
| 572 | 
            -
                        template_data.get('metadata', {}).get('color') or
         | 
| 573 | 
            -
                        template_data.get('color')
         | 
| 574 | 
            -
                    )
         | 
| 575 | 
            -
                    if color:
         | 
| 576 | 
            -
                        frontmatter_lines.append(f"color: {color}")
         | 
| 577 | 
            -
                    
         | 
| 578 | 
            -
                    frontmatter_lines.append("---")
         | 
| 579 | 
            -
                    frontmatter_lines.append("")
         | 
| 580 | 
            -
                    frontmatter_lines.append("")
         | 
| 581 | 
            -
                    
         | 
| 582 | 
            -
                    frontmatter = '\n'.join(frontmatter_lines)
         | 
| 583 | 
            -
                    
         | 
| 584 | 
            -
                    # Get the main content (instructions)
         | 
| 585 | 
            -
                    # Check multiple possible locations for instructions
         | 
| 586 | 
            -
                    content = (
         | 
| 587 | 
            -
                        template_data.get('instructions') or
         | 
| 588 | 
            -
                        template_data.get('narrative_fields', {}).get('instructions') or
         | 
| 589 | 
            -
                        template_data.get('content') or
         | 
| 590 | 
            -
                        f"You are the {agent_name} agent. Perform tasks related to {template_data.get('description', 'your specialization')}."
         | 
| 591 | 
            -
                    )
         | 
| 592 | 
            -
                    
         | 
| 593 | 
            -
                    return frontmatter + content
         | 
| 376 | 
            +
                    """Reset deployment metrics."""
         | 
| 377 | 
            +
                    return self.metrics_collector.reset_metrics()
         | 
| 594 378 |  | 
| 595 | 
            -
                def  | 
| 596 | 
            -
                     | 
| 597 | 
            -
             | 
| 598 | 
            -
                     | 
| 599 | 
            -
                    
         | 
| 600 | 
            -
                    Args:
         | 
| 601 | 
            -
                        agent_name: Name of the agent
         | 
| 602 | 
            -
                        template_path: Path to the agent template JSON file
         | 
| 603 | 
            -
                        base_agent_data: Base agent data from JSON
         | 
| 604 | 
            -
                        
         | 
| 605 | 
            -
                    Returns:
         | 
| 606 | 
            -
                        Complete agent YAML content
         | 
| 607 | 
            -
                    """
         | 
| 608 | 
            -
                    import json
         | 
| 609 | 
            -
                    
         | 
| 610 | 
            -
                    # Read template JSON
         | 
| 611 | 
            -
                    template_data = json.loads(template_path.read_text())
         | 
| 612 | 
            -
                    
         | 
| 613 | 
            -
                    # Extract capabilities
         | 
| 614 | 
            -
                    capabilities = template_data.get('capabilities', {})
         | 
| 615 | 
            -
                    metadata = template_data.get('metadata', {})
         | 
| 616 | 
            -
                    
         | 
| 617 | 
            -
                    # Extract version information
         | 
| 618 | 
            -
                    agent_version = self._parse_version(template_data.get('agent_version') or template_data.get('version', 0))
         | 
| 619 | 
            -
                    version_string = self._format_version_display(agent_version)
         | 
| 620 | 
            -
                    
         | 
| 621 | 
            -
                    # Get tools list
         | 
| 622 | 
            -
                    tools = capabilities.get('tools', [])
         | 
| 623 | 
            -
                    tools_str = ', '.join(tools) if tools else 'Read, Write, Edit, Grep, Glob, LS'
         | 
| 624 | 
            -
                    
         | 
| 625 | 
            -
                    # Get description
         | 
| 626 | 
            -
                    description = (
         | 
| 627 | 
            -
                        metadata.get('description') or
         | 
| 628 | 
            -
                        template_data.get('description') or
         | 
| 629 | 
            -
                        f'{agent_name.title()} agent for specialized tasks'
         | 
| 630 | 
            -
                    )
         | 
| 631 | 
            -
                    
         | 
| 632 | 
            -
                    # Get priority based on agent type
         | 
| 633 | 
            -
                    priority_map = {
         | 
| 634 | 
            -
                        'security': 'high',
         | 
| 635 | 
            -
                        'qa': 'high', 
         | 
| 636 | 
            -
                        'engineer': 'high',
         | 
| 637 | 
            -
                        'documentation': 'medium',
         | 
| 638 | 
            -
                        'research': 'medium',
         | 
| 639 | 
            -
                        'ops': 'high',
         | 
| 640 | 
            -
                        'data_engineer': 'medium',
         | 
| 641 | 
            -
                        'version_control': 'high'
         | 
| 642 | 
            -
                    }
         | 
| 643 | 
            -
                    priority = priority_map.get(agent_name, 'medium')
         | 
| 644 | 
            -
                    
         | 
| 645 | 
            -
                    # Get model
         | 
| 646 | 
            -
                    model = capabilities.get('model', 'claude-3-5-sonnet-20241022')
         | 
| 647 | 
            -
                    
         | 
| 648 | 
            -
                    # Get temperature
         | 
| 649 | 
            -
                    temperature = capabilities.get('temperature', 0.3)
         | 
| 650 | 
            -
                    
         | 
| 651 | 
            -
                    # Build clean YAML frontmatter with only essential fields
         | 
| 652 | 
            -
                    yaml_content = f"""---
         | 
| 653 | 
            -
            name: {agent_name}
         | 
| 654 | 
            -
            description: "{description}"
         | 
| 655 | 
            -
            version: "{version_string}"
         | 
| 656 | 
            -
            tools: {tools_str}
         | 
| 657 | 
            -
            priority: {priority}
         | 
| 658 | 
            -
            model: {model}
         | 
| 659 | 
            -
            temperature: {temperature}"""
         | 
| 660 | 
            -
                    
         | 
| 661 | 
            -
                    # Add allowed_tools if present
         | 
| 662 | 
            -
                    if 'allowed_tools' in capabilities:
         | 
| 663 | 
            -
                        yaml_content += f"\nallowed_tools: {json.dumps(capabilities['allowed_tools'])}"
         | 
| 664 | 
            -
                        
         | 
| 665 | 
            -
                    # Add disallowed_tools if present  
         | 
| 666 | 
            -
                    if 'disallowed_tools' in capabilities:
         | 
| 667 | 
            -
                        yaml_content += f"\ndisallowed_tools: {json.dumps(capabilities['disallowed_tools'])}"
         | 
| 668 | 
            -
                        
         | 
| 669 | 
            -
                    yaml_content += "\n---\n"
         | 
| 670 | 
            -
                    
         | 
| 671 | 
            -
                    # Get instructions from template
         | 
| 672 | 
            -
                    instructions = (
         | 
| 673 | 
            -
                        template_data.get('instructions') or
         | 
| 674 | 
            -
                        base_agent_data.get('narrative_fields', {}).get('instructions', '')
         | 
| 675 | 
            -
                    )
         | 
| 676 | 
            -
                    
         | 
| 677 | 
            -
                    # Add base instructions if not already included
         | 
| 678 | 
            -
                    base_instructions = base_agent_data.get('narrative_fields', {}).get('instructions', '')
         | 
| 679 | 
            -
                    if base_instructions and base_instructions not in instructions:
         | 
| 680 | 
            -
                        yaml_content += base_instructions + "\n\n---\n\n"
         | 
| 681 | 
            -
                        
         | 
| 682 | 
            -
                    yaml_content += instructions
         | 
| 683 | 
            -
                    
         | 
| 684 | 
            -
                    return yaml_content
         | 
| 685 | 
            -
                
         | 
| 686 | 
            -
                def _merge_narrative_fields(self, base_data: dict, template_data: dict) -> dict:
         | 
| 687 | 
            -
                    """
         | 
| 688 | 
            -
                    Merge narrative fields from base and template, combining arrays.
         | 
| 689 | 
            -
                    
         | 
| 690 | 
            -
                    Args:
         | 
| 691 | 
            -
                        base_data: Base agent data
         | 
| 692 | 
            -
                        template_data: Agent template data
         | 
| 693 | 
            -
                        
         | 
| 694 | 
            -
                    Returns:
         | 
| 695 | 
            -
                        Merged narrative fields
         | 
| 696 | 
            -
                    """
         | 
| 697 | 
            -
                    base_narrative = base_data.get('narrative_fields', {})
         | 
| 698 | 
            -
                    template_narrative = template_data.get('narrative_fields', {})
         | 
| 699 | 
            -
                    
         | 
| 700 | 
            -
                    merged = {}
         | 
| 701 | 
            -
                    
         | 
| 702 | 
            -
                    # For narrative fields, combine base + template
         | 
| 703 | 
            -
                    for field in ['when_to_use', 'specialized_knowledge', 'unique_capabilities']:
         | 
| 704 | 
            -
                        base_items = base_narrative.get(field, [])
         | 
| 705 | 
            -
                        template_items = template_narrative.get(field, [])
         | 
| 706 | 
            -
                        merged[field] = base_items + template_items
         | 
| 707 | 
            -
                    
         | 
| 708 | 
            -
                    # For instructions, combine with separator
         | 
| 709 | 
            -
                    base_instructions = base_narrative.get('instructions', '')
         | 
| 710 | 
            -
                    template_instructions = template_narrative.get('instructions', '')
         | 
| 711 | 
            -
                    
         | 
| 712 | 
            -
                    if base_instructions and template_instructions:
         | 
| 713 | 
            -
                        merged['instructions'] = base_instructions + "\n\n---\n\n" + template_instructions
         | 
| 714 | 
            -
                    elif template_instructions:
         | 
| 715 | 
            -
                        merged['instructions'] = template_instructions
         | 
| 716 | 
            -
                    elif base_instructions:
         | 
| 717 | 
            -
                        merged['instructions'] = base_instructions
         | 
| 718 | 
            -
                    else:
         | 
| 719 | 
            -
                        merged['instructions'] = ''
         | 
| 720 | 
            -
                        
         | 
| 721 | 
            -
                    return merged
         | 
| 722 | 
            -
                
         | 
| 723 | 
            -
                def _merge_configuration_fields(self, base_data: dict, template_data: dict) -> dict:
         | 
| 724 | 
            -
                    """
         | 
| 725 | 
            -
                    Merge configuration fields, with template overriding base.
         | 
| 726 | 
            -
                    
         | 
| 727 | 
            -
                    Args:
         | 
| 728 | 
            -
                        base_data: Base agent data
         | 
| 729 | 
            -
                        template_data: Agent template data
         | 
| 730 | 
            -
                        
         | 
| 731 | 
            -
                    Returns:
         | 
| 732 | 
            -
                        Merged configuration fields
         | 
| 733 | 
            -
                    """
         | 
| 734 | 
            -
                    base_config = base_data.get('configuration_fields', {})
         | 
| 735 | 
            -
                    template_config = template_data.get('configuration_fields', {})
         | 
| 736 | 
            -
                    
         | 
| 737 | 
            -
                    # Start with base configuration
         | 
| 738 | 
            -
                    merged = base_config.copy()
         | 
| 739 | 
            -
                    
         | 
| 740 | 
            -
                    # Override with template-specific configuration
         | 
| 741 | 
            -
                    merged.update(template_config)
         | 
| 742 | 
            -
                    
         | 
| 743 | 
            -
                    # Also merge in capabilities from new format if not already in config
         | 
| 744 | 
            -
                    capabilities = template_data.get('capabilities', {})
         | 
| 745 | 
            -
                    if capabilities:
         | 
| 746 | 
            -
                        # Map capabilities fields to configuration fields
         | 
| 747 | 
            -
                        if 'tools' not in merged and 'tools' in capabilities:
         | 
| 748 | 
            -
                            merged['tools'] = capabilities['tools']
         | 
| 749 | 
            -
                        if 'max_tokens' not in merged and 'max_tokens' in capabilities:
         | 
| 750 | 
            -
                            merged['max_tokens'] = capabilities['max_tokens']
         | 
| 751 | 
            -
                        if 'temperature' not in merged and 'temperature' in capabilities:
         | 
| 752 | 
            -
                            merged['temperature'] = capabilities['temperature']
         | 
| 753 | 
            -
                        if 'timeout' not in merged and 'timeout' in capabilities:
         | 
| 754 | 
            -
                            merged['timeout'] = capabilities['timeout']
         | 
| 755 | 
            -
                        if 'memory_limit' not in merged and 'memory_limit' in capabilities:
         | 
| 756 | 
            -
                            merged['memory_limit'] = capabilities['memory_limit']
         | 
| 757 | 
            -
                        if 'cpu_limit' not in merged and 'cpu_limit' in capabilities:
         | 
| 758 | 
            -
                            merged['cpu_limit'] = capabilities['cpu_limit']
         | 
| 759 | 
            -
                        if 'network_access' not in merged and 'network_access' in capabilities:
         | 
| 760 | 
            -
                            merged['network_access'] = capabilities['network_access']
         | 
| 761 | 
            -
                        if 'model' not in merged and 'model' in capabilities:
         | 
| 762 | 
            -
                            merged['model'] = capabilities['model']
         | 
| 763 | 
            -
                    
         | 
| 764 | 
            -
                    # Also check metadata for description and tags in new format
         | 
| 765 | 
            -
                    metadata = template_data.get('metadata', {})
         | 
| 766 | 
            -
                    if metadata:
         | 
| 767 | 
            -
                        if 'description' not in merged and 'description' in metadata:
         | 
| 768 | 
            -
                            merged['description'] = metadata['description']
         | 
| 769 | 
            -
                        if 'tags' not in merged and 'tags' in metadata:
         | 
| 770 | 
            -
                            merged['tags'] = metadata['tags']
         | 
| 771 | 
            -
                    
         | 
| 772 | 
            -
                    return merged
         | 
| 773 | 
            -
                
         | 
| 774 | 
            -
                def set_claude_environment(self, config_dir: Optional[Path] = None) -> Dict[str, str]:
         | 
| 775 | 
            -
                    """
         | 
| 776 | 
            -
                    Set Claude environment variables for agent discovery.
         | 
| 777 | 
            -
                    
         | 
| 778 | 
            -
                    OPERATIONAL PURPOSE:
         | 
| 779 | 
            -
                    Claude Code discovers agents through environment variables that
         | 
| 780 | 
            -
                    point to configuration directories. This method ensures proper
         | 
| 781 | 
            -
                    environment setup for agent runtime discovery.
         | 
| 782 | 
            -
                    
         | 
| 783 | 
            -
                    ENVIRONMENT VARIABLES SET:
         | 
| 784 | 
            -
                    1. CLAUDE_CONFIG_DIR: Root configuration directory path
         | 
| 785 | 
            -
                    2. CLAUDE_MAX_PARALLEL_SUBAGENTS: Concurrency limit (default: 5)
         | 
| 786 | 
            -
                    3. CLAUDE_TIMEOUT: Agent execution timeout (default: 600s)
         | 
| 787 | 
            -
                    
         | 
| 788 | 
            -
                    DEPLOYMENT CONSIDERATIONS:
         | 
| 789 | 
            -
                    - Call after agent deployment for immediate availability
         | 
| 790 | 
            -
                    - Environment changes affect current process and children
         | 
| 791 | 
            -
                    - Does not persist across system restarts
         | 
| 792 | 
            -
                    - Add to shell profile for permanent configuration
         | 
| 793 | 
            -
                    
         | 
| 794 | 
            -
                    TROUBLESHOOTING:
         | 
| 795 | 
            -
                    - Verify with: echo $CLAUDE_CONFIG_DIR
         | 
| 796 | 
            -
                    - Check agent discovery: ls $CLAUDE_CONFIG_DIR/agents/
         | 
| 797 | 
            -
                    - Monitor timeout issues in production
         | 
| 798 | 
            -
                    - Adjust parallel limits based on system resources
         | 
| 799 | 
            -
                    
         | 
| 800 | 
            -
                    PERFORMANCE TUNING:
         | 
| 801 | 
            -
                    - Increase parallel agents for CPU-bound tasks
         | 
| 802 | 
            -
                    - Reduce for memory-constrained environments
         | 
| 803 | 
            -
                    - Balance timeout with longest expected operations
         | 
| 804 | 
            -
                    - Monitor resource usage during parallel execution
         | 
| 805 | 
            -
                    
         | 
| 806 | 
            -
                    Args:
         | 
| 807 | 
            -
                        config_dir: Claude configuration directory (default: .claude/)
         | 
| 808 | 
            -
                        
         | 
| 809 | 
            -
                    Returns:
         | 
| 810 | 
            -
                        Dictionary of environment variables set for verification
         | 
| 811 | 
            -
                    """
         | 
| 379 | 
            +
                def set_claude_environment(
         | 
| 380 | 
            +
                    self, config_dir: Optional[Path] = None
         | 
| 381 | 
            +
                ) -> Dict[str, str]:
         | 
| 382 | 
            +
                    """Set Claude environment variables for agent discovery."""
         | 
| 812 383 | 
             
                    if not config_dir:
         | 
| 813 | 
            -
                        # Use the working directory determined during initialization
         | 
| 814 384 | 
             
                        config_dir = self.working_directory / Paths.CLAUDE_CONFIG_DIR.value
         | 
| 815 | 
            -
                    
         | 
| 816 | 
            -
             | 
| 817 | 
            -
                    
         | 
| 818 | 
            -
                    # Set Claude configuration directory
         | 
| 819 | 
            -
                    env_vars[EnvironmentVars.CLAUDE_CONFIG_DIR.value] = str(config_dir.absolute())
         | 
| 820 | 
            -
                    
         | 
| 821 | 
            -
                    # Set parallel agent limits
         | 
| 822 | 
            -
                    env_vars[EnvironmentVars.CLAUDE_MAX_PARALLEL_SUBAGENTS.value] = EnvironmentVars.DEFAULT_MAX_AGENTS.value
         | 
| 823 | 
            -
                    
         | 
| 824 | 
            -
                    # Set timeout for agent execution
         | 
| 825 | 
            -
                    env_vars[EnvironmentVars.CLAUDE_TIMEOUT.value] = EnvironmentVars.DEFAULT_TIMEOUT.value
         | 
| 826 | 
            -
                    
         | 
| 827 | 
            -
                    # Apply environment variables
         | 
| 828 | 
            -
                    for key, value in env_vars.items():
         | 
| 829 | 
            -
                        os.environ[key] = value
         | 
| 830 | 
            -
                        self.logger.debug(f"Set environment: {key}={value}")
         | 
| 831 | 
            -
                    
         | 
| 832 | 
            -
                    return env_vars
         | 
| 833 | 
            -
                
         | 
| 385 | 
            +
                    return self.environment_manager.set_claude_environment(config_dir)
         | 
| 386 | 
            +
             | 
| 834 387 | 
             
                def verify_deployment(self, config_dir: Optional[Path] = None) -> Dict[str, Any]:
         | 
| 835 | 
            -
                    """
         | 
| 836 | 
            -
                    Verify agent deployment and Claude configuration.
         | 
| 837 | 
            -
                    
         | 
| 838 | 
            -
                    OPERATIONAL PURPOSE:
         | 
| 839 | 
            -
                    Post-deployment verification ensures agents are correctly deployed
         | 
| 840 | 
            -
                    and discoverable by Claude Code. Critical for deployment validation
         | 
| 841 | 
            -
                    and troubleshooting runtime issues.
         | 
| 842 | 
            -
                    
         | 
| 843 | 
            -
                    VERIFICATION CHECKS:
         | 
| 844 | 
            -
                    1. Configuration directory exists and is accessible
         | 
| 845 | 
            -
                    2. Agents directory contains expected YAML files
         | 
| 846 | 
            -
                    3. Agent files have valid YAML frontmatter
         | 
| 847 | 
            -
                    4. Version format is current (identifies migration needs)
         | 
| 848 | 
            -
                    5. Environment variables are properly set
         | 
| 849 | 
            -
                    
         | 
| 850 | 
            -
                    MONITORING INTEGRATION:
         | 
| 851 | 
            -
                    - Call after deployment for health checks
         | 
| 852 | 
            -
                    - Include in deployment pipelines
         | 
| 853 | 
            -
                    - Log results for audit trails
         | 
| 854 | 
            -
                    - Alert on missing agents or errors
         | 
| 855 | 
            -
                    
         | 
| 856 | 
            -
                    TROUBLESHOOTING GUIDE:
         | 
| 857 | 
            -
                    - Missing config_dir: Check deployment target path
         | 
| 858 | 
            -
                    - No agents found: Verify deployment completed
         | 
| 859 | 
            -
                    - Migration needed: Run with force_rebuild
         | 
| 860 | 
            -
                    - Environment warnings: Call set_claude_environment()
         | 
| 861 | 
            -
                    
         | 
| 862 | 
            -
                    RESULT INTERPRETATION:
         | 
| 863 | 
            -
                    - agents_found: Successfully deployed agents
         | 
| 864 | 
            -
                    - agents_needing_migration: Require version update
         | 
| 865 | 
            -
                    - warnings: Non-critical issues to address
         | 
| 866 | 
            -
                    - environment: Current runtime configuration
         | 
| 867 | 
            -
                    
         | 
| 868 | 
            -
                    Args:
         | 
| 869 | 
            -
                        config_dir: Claude configuration directory (default: .claude/)
         | 
| 870 | 
            -
                        
         | 
| 871 | 
            -
                    Returns:
         | 
| 872 | 
            -
                        Verification results dictionary:
         | 
| 873 | 
            -
                        - config_dir: Checked directory path
         | 
| 874 | 
            -
                        - agents_found: List of discovered agents with metadata
         | 
| 875 | 
            -
                        - agents_needing_migration: Agents with old version format
         | 
| 876 | 
            -
                        - environment: Current environment variables
         | 
| 877 | 
            -
                        - warnings: List of potential issues
         | 
| 878 | 
            -
                    """
         | 
| 388 | 
            +
                    """Verify agent deployment and Claude configuration."""
         | 
| 879 389 | 
             
                    if not config_dir:
         | 
| 880 | 
            -
                        # Use the working directory determined during initialization
         | 
| 881 390 | 
             
                        config_dir = self.working_directory / ".claude"
         | 
| 882 | 
            -
                    
         | 
| 883 | 
            -
             | 
| 884 | 
            -
             | 
| 885 | 
            -
             | 
| 886 | 
            -
             | 
| 887 | 
            -
                        "environment": {},
         | 
| 888 | 
            -
                        "warnings": []
         | 
| 889 | 
            -
                    }
         | 
| 890 | 
            -
                    
         | 
| 891 | 
            -
                    # Check configuration directory
         | 
| 892 | 
            -
                    if not config_dir.exists():
         | 
| 893 | 
            -
                        results["warnings"].append(f"Configuration directory not found: {config_dir}")
         | 
| 894 | 
            -
                        return results
         | 
| 895 | 
            -
                    
         | 
| 896 | 
            -
                    # Check agents directory
         | 
| 897 | 
            -
                    agents_dir = config_dir / "agents"
         | 
| 898 | 
            -
                    if not agents_dir.exists():
         | 
| 899 | 
            -
                        results["warnings"].append(f"Agents directory not found: {agents_dir}")
         | 
| 900 | 
            -
                        return results
         | 
| 901 | 
            -
                    
         | 
| 902 | 
            -
                    # List deployed agents
         | 
| 903 | 
            -
                    agent_files = list(agents_dir.glob("*.md"))
         | 
| 904 | 
            -
                    
         | 
| 905 | 
            -
                    # Get exclusion configuration for logging purposes
         | 
| 906 | 
            -
                    try:
         | 
| 907 | 
            -
                        from claude_mpm.core.config import Config
         | 
| 908 | 
            -
                        config = Config()
         | 
| 909 | 
            -
                        excluded_agents = config.get('agent_deployment.excluded_agents', [])
         | 
| 910 | 
            -
                        if excluded_agents:
         | 
| 911 | 
            -
                            self.logger.debug(f"Note: The following agents are configured for exclusion: {excluded_agents}")
         | 
| 912 | 
            -
                    except Exception:
         | 
| 913 | 
            -
                        pass  # Ignore config loading errors in verification
         | 
| 914 | 
            -
                    
         | 
| 915 | 
            -
                    for agent_file in agent_files:
         | 
| 916 | 
            -
                        try:
         | 
| 917 | 
            -
                            # Read first few lines to get agent name from YAML
         | 
| 918 | 
            -
                            with open(agent_file, 'r') as f:
         | 
| 919 | 
            -
                                lines = f.readlines()[:10]
         | 
| 920 | 
            -
                                
         | 
| 921 | 
            -
                            agent_info = {
         | 
| 922 | 
            -
                                "file": agent_file.name,
         | 
| 923 | 
            -
                                "path": str(agent_file)
         | 
| 924 | 
            -
                            }
         | 
| 925 | 
            -
                            
         | 
| 926 | 
            -
                            # Extract name, version, and base_version from YAML frontmatter
         | 
| 927 | 
            -
                            version_str = None
         | 
| 928 | 
            -
                            base_version_str = None
         | 
| 929 | 
            -
                            for line in lines:
         | 
| 930 | 
            -
                                if line.startswith("name:"):
         | 
| 931 | 
            -
                                    agent_info["name"] = line.split(":", 1)[1].strip().strip('"\'')
         | 
| 932 | 
            -
                                elif line.startswith("version:"):
         | 
| 933 | 
            -
                                    version_str = line.split(":", 1)[1].strip().strip('"\'')
         | 
| 934 | 
            -
                                    agent_info["version"] = version_str
         | 
| 935 | 
            -
                                elif line.startswith("base_version:"):
         | 
| 936 | 
            -
                                    base_version_str = line.split(":", 1)[1].strip().strip('"\'')
         | 
| 937 | 
            -
                                    agent_info["base_version"] = base_version_str
         | 
| 938 | 
            -
                            
         | 
| 939 | 
            -
                            # Check if agent needs migration
         | 
| 940 | 
            -
                            if version_str and self._is_old_version_format(version_str):
         | 
| 941 | 
            -
                                agent_info["needs_migration"] = True
         | 
| 942 | 
            -
                                results["agents_needing_migration"].append(agent_info["name"])
         | 
| 943 | 
            -
                            
         | 
| 944 | 
            -
                            results["agents_found"].append(agent_info)
         | 
| 945 | 
            -
                            
         | 
| 946 | 
            -
                        except Exception as e:
         | 
| 947 | 
            -
                            results["warnings"].append(f"Failed to read {agent_file.name}: {e}")
         | 
| 948 | 
            -
                    
         | 
| 949 | 
            -
                    # Check environment variables
         | 
| 950 | 
            -
                    env_vars = ["CLAUDE_CONFIG_DIR", "CLAUDE_MAX_PARALLEL_SUBAGENTS", "CLAUDE_TIMEOUT"]
         | 
| 951 | 
            -
                    for var in env_vars:
         | 
| 952 | 
            -
                        value = os.environ.get(var)
         | 
| 953 | 
            -
                        if value:
         | 
| 954 | 
            -
                            results["environment"][var] = value
         | 
| 955 | 
            -
                        else:
         | 
| 956 | 
            -
                            results["warnings"].append(f"Environment variable not set: {var}")
         | 
| 957 | 
            -
                    
         | 
| 958 | 
            -
                    return results
         | 
| 959 | 
            -
                
         | 
| 960 | 
            -
                def deploy_agent(self, agent_name: str, target_dir: Path, force_rebuild: bool = False) -> bool:
         | 
| 391 | 
            +
                    return self.validator.verify_deployment(config_dir)
         | 
| 392 | 
            +
             | 
| 393 | 
            +
                def deploy_agent(
         | 
| 394 | 
            +
                    self, agent_name: str, target_dir: Path, force_rebuild: bool = False
         | 
| 395 | 
            +
                ) -> bool:
         | 
| 961 396 | 
             
                    """
         | 
| 962 397 | 
             
                    Deploy a single agent to the specified directory.
         | 
| 963 | 
            -
             | 
| 398 | 
            +
             | 
| 964 399 | 
             
                    Args:
         | 
| 965 400 | 
             
                        agent_name: Name of the agent to deploy
         | 
| 966 401 | 
             
                        target_dir: Target directory for deployment (Path object)
         | 
| 967 402 | 
             
                        force_rebuild: Whether to force rebuild even if version is current
         | 
| 968 | 
            -
             | 
| 403 | 
            +
             | 
| 969 404 | 
             
                    Returns:
         | 
| 970 405 | 
             
                        True if deployment was successful, False otherwise
         | 
| 971 | 
            -
             | 
| 406 | 
            +
             | 
| 972 407 | 
             
                    WHY: Single agent deployment because:
         | 
| 973 408 | 
             
                    - Users may want to deploy specific agents only
         | 
| 974 409 | 
             
                    - Reduces deployment time for targeted updates
         | 
| 975 410 | 
             
                    - Enables selective agent management in projects
         | 
| 976 | 
            -
             | 
| 411 | 
            +
             | 
| 977 412 | 
             
                    FIXED: Method now correctly handles all internal calls to:
         | 
| 978 413 | 
             
                    - _check_agent_needs_update (with 3 arguments)
         | 
| 979 414 | 
             
                    - _build_agent_markdown (with 3 arguments including base_agent_data)
         | 
| @@ -985,14 +420,14 @@ temperature: {temperature}""" | |
| 985 420 | 
             
                        if not template_file.exists():
         | 
| 986 421 | 
             
                            self.logger.error(f"Agent template not found: {agent_name}")
         | 
| 987 422 | 
             
                            return False
         | 
| 988 | 
            -
             | 
| 423 | 
            +
             | 
| 989 424 | 
             
                        # Ensure target directory exists
         | 
| 990 | 
            -
                        agents_dir = target_dir /  | 
| 425 | 
            +
                        agents_dir = target_dir / ".claude" / "agents"
         | 
| 991 426 | 
             
                        agents_dir.mkdir(parents=True, exist_ok=True)
         | 
| 992 | 
            -
             | 
| 427 | 
            +
             | 
| 993 428 | 
             
                        # Build and deploy the agent
         | 
| 994 429 | 
             
                        target_file = agents_dir / f"{agent_name}.md"
         | 
| 995 | 
            -
             | 
| 430 | 
            +
             | 
| 996 431 | 
             
                        # Check if update is needed
         | 
| 997 432 | 
             
                        if not force_rebuild and target_file.exists():
         | 
| 998 433 | 
             
                            # Load base agent data for version checking
         | 
| @@ -1001,39 +436,52 @@ temperature: {temperature}""" | |
| 1001 436 | 
             
                            if self.base_agent_path.exists():
         | 
| 1002 437 | 
             
                                try:
         | 
| 1003 438 | 
             
                                    import json
         | 
| 439 | 
            +
             | 
| 1004 440 | 
             
                                    base_agent_data = json.loads(self.base_agent_path.read_text())
         | 
| 1005 | 
            -
                                    base_agent_version = self. | 
| 441 | 
            +
                                    base_agent_version = self.version_manager.parse_version(
         | 
| 442 | 
            +
                                        base_agent_data.get("base_version")
         | 
| 443 | 
            +
                                        or base_agent_data.get("version", 0)
         | 
| 444 | 
            +
                                    )
         | 
| 1006 445 | 
             
                                except Exception as e:
         | 
| 1007 | 
            -
                                    self.logger.warning( | 
| 1008 | 
            -
             | 
| 1009 | 
            -
             | 
| 446 | 
            +
                                    self.logger.warning(
         | 
| 447 | 
            +
                                        f"Could not load base agent for version check: {e}"
         | 
| 448 | 
            +
                                    )
         | 
| 449 | 
            +
             | 
| 450 | 
            +
                            needs_update, reason = self.version_manager.check_agent_needs_update(
         | 
| 451 | 
            +
                                target_file, template_file, base_agent_version
         | 
| 452 | 
            +
                            )
         | 
| 1010 453 | 
             
                            if not needs_update:
         | 
| 1011 454 | 
             
                                self.logger.info(f"Agent {agent_name} is up to date")
         | 
| 1012 455 | 
             
                                return True
         | 
| 1013 456 | 
             
                            else:
         | 
| 1014 457 | 
             
                                self.logger.info(f"Updating agent {agent_name}: {reason}")
         | 
| 1015 | 
            -
             | 
| 458 | 
            +
             | 
| 1016 459 | 
             
                        # Load base agent data for building
         | 
| 1017 460 | 
             
                        base_agent_data = {}
         | 
| 1018 461 | 
             
                        if self.base_agent_path.exists():
         | 
| 1019 462 | 
             
                            try:
         | 
| 1020 463 | 
             
                                import json
         | 
| 464 | 
            +
             | 
| 1021 465 | 
             
                                base_agent_data = json.loads(self.base_agent_path.read_text())
         | 
| 1022 466 | 
             
                            except Exception as e:
         | 
| 1023 467 | 
             
                                self.logger.warning(f"Could not load base agent: {e}")
         | 
| 1024 | 
            -
             | 
| 468 | 
            +
             | 
| 1025 469 | 
             
                        # Build the agent markdown
         | 
| 1026 | 
            -
                        agent_content = self. | 
| 470 | 
            +
                        agent_content = self.template_builder.build_agent_markdown(
         | 
| 471 | 
            +
                            agent_name, template_file, base_agent_data
         | 
| 472 | 
            +
                        )
         | 
| 1027 473 | 
             
                        if not agent_content:
         | 
| 1028 474 | 
             
                            self.logger.error(f"Failed to build agent content for {agent_name}")
         | 
| 1029 475 | 
             
                            return False
         | 
| 1030 | 
            -
             | 
| 476 | 
            +
             | 
| 1031 477 | 
             
                        # Write to target file
         | 
| 1032 478 | 
             
                        target_file.write_text(agent_content)
         | 
| 1033 | 
            -
                        self.logger.info( | 
| 1034 | 
            -
             | 
| 479 | 
            +
                        self.logger.info(
         | 
| 480 | 
            +
                            f"Successfully deployed agent: {agent_name} to {target_file}"
         | 
| 481 | 
            +
                        )
         | 
| 482 | 
            +
             | 
| 1035 483 | 
             
                        return True
         | 
| 1036 | 
            -
             | 
| 484 | 
            +
             | 
| 1037 485 | 
             
                    except AgentDeploymentError:
         | 
| 1038 486 | 
             
                        # Re-raise our custom exceptions
         | 
| 1039 487 | 
             
                        raise
         | 
| @@ -1041,943 +489,83 @@ temperature: {temperature}""" | |
| 1041 489 | 
             
                        # Wrap generic exceptions with context
         | 
| 1042 490 | 
             
                        raise AgentDeploymentError(
         | 
| 1043 491 | 
             
                            f"Failed to deploy agent {agent_name}",
         | 
| 1044 | 
            -
                            context={"agent_name": agent_name, "error": str(e)}
         | 
| 492 | 
            +
                            context={"agent_name": agent_name, "error": str(e)},
         | 
| 1045 493 | 
             
                        ) from e
         | 
| 1046 | 
            -
             | 
| 494 | 
            +
             | 
| 1047 495 | 
             
                def list_available_agents(self) -> List[Dict[str, Any]]:
         | 
| 1048 | 
            -
                    """
         | 
| 1049 | 
            -
                     | 
| 1050 | 
            -
             | 
| 1051 | 
            -
                    Returns:
         | 
| 1052 | 
            -
                        List of agent information dictionaries
         | 
| 1053 | 
            -
                    """
         | 
| 1054 | 
            -
                    agents = []
         | 
| 1055 | 
            -
                    
         | 
| 1056 | 
            -
                    if not self.templates_dir.exists():
         | 
| 1057 | 
            -
                        self.logger.warning(f"Agents directory not found: {self.templates_dir}")
         | 
| 1058 | 
            -
                        return agents
         | 
| 1059 | 
            -
                    
         | 
| 1060 | 
            -
                    template_files = sorted(self.templates_dir.glob("*.json"))
         | 
| 1061 | 
            -
                    
         | 
| 1062 | 
            -
                    # Load configuration for exclusions
         | 
| 1063 | 
            -
                    try:
         | 
| 1064 | 
            -
                        from claude_mpm.core.config import Config
         | 
| 1065 | 
            -
                        config = Config()
         | 
| 1066 | 
            -
                        excluded_agents = config.get('agent_deployment.excluded_agents', [])
         | 
| 1067 | 
            -
                        case_sensitive = config.get('agent_deployment.case_sensitive', False)
         | 
| 1068 | 
            -
                        
         | 
| 1069 | 
            -
                        # Normalize excluded agents for comparison
         | 
| 1070 | 
            -
                        if not case_sensitive:
         | 
| 1071 | 
            -
                            excluded_agents = [agent.lower() for agent in excluded_agents]
         | 
| 1072 | 
            -
                    except Exception:
         | 
| 1073 | 
            -
                        # If config loading fails, use empty exclusion list
         | 
| 1074 | 
            -
                        excluded_agents = []
         | 
| 1075 | 
            -
                        case_sensitive = False
         | 
| 1076 | 
            -
                    
         | 
| 1077 | 
            -
                    # Build combined exclusion set
         | 
| 1078 | 
            -
                    hardcoded_exclusions = {"__init__", "MEMORIES", "TODOWRITE", "INSTRUCTIONS", "README", "pm", "PM", "project_manager"}
         | 
| 1079 | 
            -
                    
         | 
| 1080 | 
            -
                    # Filter out excluded agents
         | 
| 1081 | 
            -
                    filtered_files = []
         | 
| 1082 | 
            -
                    for f in template_files:
         | 
| 1083 | 
            -
                        agent_name = f.stem
         | 
| 1084 | 
            -
                        
         | 
| 1085 | 
            -
                        # Check hardcoded exclusions
         | 
| 1086 | 
            -
                        if agent_name in hardcoded_exclusions:
         | 
| 1087 | 
            -
                            continue
         | 
| 1088 | 
            -
                        
         | 
| 1089 | 
            -
                        # Check file patterns
         | 
| 1090 | 
            -
                        if agent_name.startswith(".") or agent_name.endswith(".backup"):
         | 
| 1091 | 
            -
                            continue
         | 
| 1092 | 
            -
                        
         | 
| 1093 | 
            -
                        # Check user-configured exclusions
         | 
| 1094 | 
            -
                        compare_name = agent_name.lower() if not case_sensitive else agent_name
         | 
| 1095 | 
            -
                        if compare_name in excluded_agents:
         | 
| 1096 | 
            -
                            continue
         | 
| 1097 | 
            -
                        
         | 
| 1098 | 
            -
                        filtered_files.append(f)
         | 
| 1099 | 
            -
                    
         | 
| 1100 | 
            -
                    template_files = filtered_files
         | 
| 1101 | 
            -
                    
         | 
| 1102 | 
            -
                    for template_file in template_files:
         | 
| 1103 | 
            -
                        try:
         | 
| 1104 | 
            -
                            agent_name = template_file.stem
         | 
| 1105 | 
            -
                            agent_info = {
         | 
| 1106 | 
            -
                                "name": agent_name,
         | 
| 1107 | 
            -
                                "file": template_file.name,
         | 
| 1108 | 
            -
                                "path": str(template_file),
         | 
| 1109 | 
            -
                                "size": template_file.stat().st_size,
         | 
| 1110 | 
            -
                                "description": f"{agent_name.title()} agent for specialized tasks"
         | 
| 1111 | 
            -
                            }
         | 
| 1112 | 
            -
                            
         | 
| 1113 | 
            -
                            # Try to extract metadata from template JSON
         | 
| 1114 | 
            -
                            try:
         | 
| 1115 | 
            -
                                import json
         | 
| 1116 | 
            -
                                template_data = json.loads(template_file.read_text())
         | 
| 1117 | 
            -
                                
         | 
| 1118 | 
            -
                                # Handle different schema formats
         | 
| 1119 | 
            -
                                if 'metadata' in template_data:
         | 
| 1120 | 
            -
                                    # New schema format
         | 
| 1121 | 
            -
                                    metadata = template_data.get('metadata', {})
         | 
| 1122 | 
            -
                                    agent_info["description"] = metadata.get('description', agent_info["description"])
         | 
| 1123 | 
            -
                                    agent_info["role"] = metadata.get('specializations', [''])[0] if metadata.get('specializations') else ''
         | 
| 1124 | 
            -
                                elif 'configuration_fields' in template_data:
         | 
| 1125 | 
            -
                                    # Old schema format
         | 
| 1126 | 
            -
                                    config_fields = template_data.get('configuration_fields', {})
         | 
| 1127 | 
            -
                                    agent_info["role"] = config_fields.get('primary_role', '')
         | 
| 1128 | 
            -
                                    agent_info["description"] = config_fields.get('description', agent_info["description"])
         | 
| 1129 | 
            -
                                
         | 
| 1130 | 
            -
                                # Handle both 'agent_version' (new format) and 'version' (old format)
         | 
| 1131 | 
            -
                                version_tuple = self._parse_version(template_data.get('agent_version') or template_data.get('version', 0))
         | 
| 1132 | 
            -
                                agent_info["version"] = self._format_version_display(version_tuple)
         | 
| 1133 | 
            -
                            
         | 
| 1134 | 
            -
                            except Exception:
         | 
| 1135 | 
            -
                                pass  # Use defaults if can't parse
         | 
| 1136 | 
            -
                            
         | 
| 1137 | 
            -
                            agents.append(agent_info)
         | 
| 1138 | 
            -
                            
         | 
| 1139 | 
            -
                        except Exception as e:
         | 
| 1140 | 
            -
                            self.logger.error(f"Failed to read template {template_file.name}: {e}")
         | 
| 1141 | 
            -
                    
         | 
| 1142 | 
            -
                    return agents
         | 
| 1143 | 
            -
                
         | 
| 1144 | 
            -
                def _check_agent_needs_update(self, deployed_file: Path, template_file: Path, current_base_version: tuple) -> tuple:
         | 
| 1145 | 
            -
                    """
         | 
| 1146 | 
            -
                    Check if a deployed agent needs to be updated.
         | 
| 1147 | 
            -
                    
         | 
| 1148 | 
            -
                    OPERATIONAL LOGIC:
         | 
| 1149 | 
            -
                    1. Verifies agent is system-managed (claude-mpm authored)
         | 
| 1150 | 
            -
                    2. Extracts version from deployed YAML frontmatter
         | 
| 1151 | 
            -
                    3. Detects old version formats requiring migration
         | 
| 1152 | 
            -
                    4. Compares semantic versions for update decision
         | 
| 1153 | 
            -
                    5. Returns detailed reason for update/skip decision
         | 
| 1154 | 
            -
                    
         | 
| 1155 | 
            -
                    VERSION MIGRATION STRATEGY:
         | 
| 1156 | 
            -
                    - Old serial format (0002-0005) -> Semantic (2.5.0)
         | 
| 1157 | 
            -
                    - Missing versions -> Force update to latest
         | 
| 1158 | 
            -
                    - Non-semantic formats -> Trigger migration
         | 
| 1159 | 
            -
                    - Preserves user modifications (non-system agents)
         | 
| 1160 | 
            -
                    
         | 
| 1161 | 
            -
                    PERFORMANCE OPTIMIZATION:
         | 
| 1162 | 
            -
                    - Early exit for non-system agents
         | 
| 1163 | 
            -
                    - Regex compilation cached by Python
         | 
| 1164 | 
            -
                    - Minimal file reads (frontmatter only)
         | 
| 1165 | 
            -
                    - Version comparison without full parse
         | 
| 1166 | 
            -
                    
         | 
| 1167 | 
            -
                    ERROR RECOVERY:
         | 
| 1168 | 
            -
                    - Assumes update needed on parse failures
         | 
| 1169 | 
            -
                    - Logs warnings for investigation
         | 
| 1170 | 
            -
                    - Never blocks deployment pipeline
         | 
| 1171 | 
            -
                    - Safe fallback to force update
         | 
| 1172 | 
            -
                    
         | 
| 1173 | 
            -
                    Args:
         | 
| 1174 | 
            -
                        deployed_file: Path to the deployed agent file
         | 
| 1175 | 
            -
                        template_file: Path to the template file
         | 
| 1176 | 
            -
                        current_base_version: Current base agent version (unused in new strategy)
         | 
| 1177 | 
            -
                        
         | 
| 1178 | 
            -
                    Returns:
         | 
| 1179 | 
            -
                        Tuple of (needs_update: bool, reason: str)
         | 
| 1180 | 
            -
                        - needs_update: True if agent should be redeployed
         | 
| 1181 | 
            -
                        - reason: Human-readable explanation for decision
         | 
| 1182 | 
            -
                    """
         | 
| 1183 | 
            -
                    try:
         | 
| 1184 | 
            -
                        # Read deployed agent content
         | 
| 1185 | 
            -
                        deployed_content = deployed_file.read_text()
         | 
| 1186 | 
            -
                        
         | 
| 1187 | 
            -
                        # Check if it's a system agent (authored by claude-mpm)
         | 
| 1188 | 
            -
                        if "claude-mpm" not in deployed_content:
         | 
| 1189 | 
            -
                            return (False, "not a system agent")
         | 
| 1190 | 
            -
                        
         | 
| 1191 | 
            -
                        # Extract version info from YAML frontmatter
         | 
| 1192 | 
            -
                        import re
         | 
| 1193 | 
            -
                        
         | 
| 1194 | 
            -
                        # Check if using old serial format first
         | 
| 1195 | 
            -
                        is_old_format = False
         | 
| 1196 | 
            -
                        old_version_str = None
         | 
| 1197 | 
            -
                        
         | 
| 1198 | 
            -
                        # Try legacy combined format (e.g., "0002-0005")
         | 
| 1199 | 
            -
                        legacy_match = re.search(r'^version:\s*["\']?(\d+)-(\d+)["\']?', deployed_content, re.MULTILINE)
         | 
| 1200 | 
            -
                        if legacy_match:
         | 
| 1201 | 
            -
                            is_old_format = True
         | 
| 1202 | 
            -
                            old_version_str = f"{legacy_match.group(1)}-{legacy_match.group(2)}"
         | 
| 1203 | 
            -
                            # Convert legacy format to semantic version
         | 
| 1204 | 
            -
                            # Treat the agent version (second number) as minor version
         | 
| 1205 | 
            -
                            deployed_agent_version = (0, int(legacy_match.group(2)), 0)
         | 
| 1206 | 
            -
                            self.logger.info(f"Detected old serial version format: {old_version_str}")
         | 
| 1207 | 
            -
                        else:
         | 
| 1208 | 
            -
                            # Try to extract semantic version format (e.g., "2.1.0")
         | 
| 1209 | 
            -
                            version_match = re.search(r'^version:\s*["\']?v?(\d+)\.(\d+)\.(\d+)["\']?', deployed_content, re.MULTILINE)
         | 
| 1210 | 
            -
                            if version_match:
         | 
| 1211 | 
            -
                                deployed_agent_version = (int(version_match.group(1)), int(version_match.group(2)), int(version_match.group(3)))
         | 
| 1212 | 
            -
                            else:
         | 
| 1213 | 
            -
                                # Fallback: try separate fields (very old format)
         | 
| 1214 | 
            -
                                agent_version_match = re.search(r"^agent_version:\s*(\d+)", deployed_content, re.MULTILINE)
         | 
| 1215 | 
            -
                                if agent_version_match:
         | 
| 1216 | 
            -
                                    is_old_format = True
         | 
| 1217 | 
            -
                                    old_version_str = f"agent_version: {agent_version_match.group(1)}"
         | 
| 1218 | 
            -
                                    deployed_agent_version = (0, int(agent_version_match.group(1)), 0)
         | 
| 1219 | 
            -
                                    self.logger.info(f"Detected old separate version format: {old_version_str}")
         | 
| 1220 | 
            -
                                else:
         | 
| 1221 | 
            -
                                    # Check for missing version field
         | 
| 1222 | 
            -
                                    if "version:" not in deployed_content:
         | 
| 1223 | 
            -
                                        is_old_format = True
         | 
| 1224 | 
            -
                                        old_version_str = "missing"
         | 
| 1225 | 
            -
                                        deployed_agent_version = (0, 0, 0)
         | 
| 1226 | 
            -
                                        self.logger.info("Detected missing version field")
         | 
| 1227 | 
            -
                                    else:
         | 
| 1228 | 
            -
                                        deployed_agent_version = (0, 0, 0)
         | 
| 1229 | 
            -
                        
         | 
| 1230 | 
            -
                        # For base version, we don't need to extract from deployed file anymore
         | 
| 1231 | 
            -
                        # as it's tracked in metadata
         | 
| 1232 | 
            -
                        
         | 
| 1233 | 
            -
                        # Read template to get current agent version
         | 
| 1234 | 
            -
                        import json
         | 
| 1235 | 
            -
                        template_data = json.loads(template_file.read_text())
         | 
| 1236 | 
            -
                        
         | 
| 1237 | 
            -
                        # Extract agent version from template (handle both numeric and semantic versioning)
         | 
| 1238 | 
            -
                        current_agent_version = self._parse_version(template_data.get('agent_version') or template_data.get('version', 0))
         | 
| 1239 | 
            -
                        
         | 
| 1240 | 
            -
                        # Compare semantic versions properly
         | 
| 1241 | 
            -
                        # Semantic version comparison: compare major, then minor, then patch
         | 
| 1242 | 
            -
                        def compare_versions(v1: tuple, v2: tuple) -> int:
         | 
| 1243 | 
            -
                            """Compare two version tuples. Returns -1 if v1 < v2, 0 if equal, 1 if v1 > v2."""
         | 
| 1244 | 
            -
                            for a, b in zip(v1, v2):
         | 
| 1245 | 
            -
                                if a < b:
         | 
| 1246 | 
            -
                                    return -1
         | 
| 1247 | 
            -
                                elif a > b:
         | 
| 1248 | 
            -
                                    return 1
         | 
| 1249 | 
            -
                            return 0
         | 
| 1250 | 
            -
                        
         | 
| 1251 | 
            -
                        # If old format detected, always trigger update for migration
         | 
| 1252 | 
            -
                        if is_old_format:
         | 
| 1253 | 
            -
                            new_version_str = self._format_version_display(current_agent_version)
         | 
| 1254 | 
            -
                            return (True, f"migration needed from old format ({old_version_str}) to semantic version ({new_version_str})")
         | 
| 1255 | 
            -
                        
         | 
| 1256 | 
            -
                        # Check if agent template version is newer
         | 
| 1257 | 
            -
                        if compare_versions(current_agent_version, deployed_agent_version) > 0:
         | 
| 1258 | 
            -
                            deployed_str = self._format_version_display(deployed_agent_version)
         | 
| 1259 | 
            -
                            current_str = self._format_version_display(current_agent_version)
         | 
| 1260 | 
            -
                            return (True, f"agent template updated ({deployed_str} -> {current_str})")
         | 
| 1261 | 
            -
                        
         | 
| 1262 | 
            -
                        # Note: We no longer check base agent version separately since we're using
         | 
| 1263 | 
            -
                        # a unified semantic version for the agent
         | 
| 1264 | 
            -
                        
         | 
| 1265 | 
            -
                        return (False, "up to date")
         | 
| 1266 | 
            -
                        
         | 
| 1267 | 
            -
                    except Exception as e:
         | 
| 1268 | 
            -
                        self.logger.warning(f"Error checking agent update status: {e}")
         | 
| 1269 | 
            -
                        # On error, assume update is needed
         | 
| 1270 | 
            -
                        return (True, "version check failed")
         | 
| 1271 | 
            -
                
         | 
| 496 | 
            +
                    """List available agent templates."""
         | 
| 497 | 
            +
                    return self.discovery_service.list_available_agents()
         | 
| 498 | 
            +
             | 
| 1272 499 | 
             
                def clean_deployment(self, config_dir: Optional[Path] = None) -> Dict[str, Any]:
         | 
| 1273 | 
            -
                    """
         | 
| 1274 | 
            -
                    Clean up deployed agents.
         | 
| 1275 | 
            -
                    
         | 
| 1276 | 
            -
                    Args:
         | 
| 1277 | 
            -
                        config_dir: Claude configuration directory (default: .claude/)
         | 
| 1278 | 
            -
                        
         | 
| 1279 | 
            -
                    Returns:
         | 
| 1280 | 
            -
                        Cleanup results
         | 
| 1281 | 
            -
                    """
         | 
| 500 | 
            +
                    """Clean up deployed agents."""
         | 
| 1282 501 | 
             
                    if not config_dir:
         | 
| 1283 | 
            -
                        # Use the working directory determined during initialization
         | 
| 1284 502 | 
             
                        config_dir = self.working_directory / ".claude"
         | 
| 1285 | 
            -
                    
         | 
| 1286 | 
            -
             | 
| 1287 | 
            -
                        "removed": [],
         | 
| 1288 | 
            -
                        "errors": []
         | 
| 1289 | 
            -
                    }
         | 
| 1290 | 
            -
                    
         | 
| 1291 | 
            -
                    agents_dir = config_dir / "agents"
         | 
| 1292 | 
            -
                    if not agents_dir.exists():
         | 
| 1293 | 
            -
                        results["errors"].append(f"Agents directory not found: {agents_dir}")
         | 
| 1294 | 
            -
                        return results
         | 
| 1295 | 
            -
                    
         | 
| 1296 | 
            -
                    # Remove system agents only (identified by claude-mpm author)
         | 
| 1297 | 
            -
                    agent_files = list(agents_dir.glob("*.md"))
         | 
| 1298 | 
            -
                    
         | 
| 1299 | 
            -
                    for agent_file in agent_files:
         | 
| 1300 | 
            -
                        try:
         | 
| 1301 | 
            -
                            # Check if it's a system agent
         | 
| 1302 | 
            -
                            with open(agent_file, 'r') as f:
         | 
| 1303 | 
            -
                                content = f.read()
         | 
| 1304 | 
            -
                                if "author: claude-mpm" in content or "author: 'claude-mpm'" in content:
         | 
| 1305 | 
            -
                                    agent_file.unlink()
         | 
| 1306 | 
            -
                                    results["removed"].append(str(agent_file))
         | 
| 1307 | 
            -
                                    self.logger.debug(f"Removed agent: {agent_file.name}")
         | 
| 1308 | 
            -
                            
         | 
| 1309 | 
            -
                        except Exception as e:
         | 
| 1310 | 
            -
                            error_msg = f"Failed to remove {agent_file.name}: {e}"
         | 
| 1311 | 
            -
                            self.logger.error(error_msg)
         | 
| 1312 | 
            -
                            results["errors"].append(error_msg)
         | 
| 1313 | 
            -
                            # Not raising AgentDeploymentError here to continue cleanup
         | 
| 1314 | 
            -
                    
         | 
| 1315 | 
            -
                    return results
         | 
| 1316 | 
            -
                
         | 
| 1317 | 
            -
                def _extract_agent_metadata(self, template_content: str) -> Dict[str, Any]:
         | 
| 1318 | 
            -
                    """
         | 
| 1319 | 
            -
                    Extract metadata from simplified agent template content.
         | 
| 1320 | 
            -
                    
         | 
| 1321 | 
            -
                    Args:
         | 
| 1322 | 
            -
                        template_content: Agent template markdown content
         | 
| 1323 | 
            -
                        
         | 
| 1324 | 
            -
                    Returns:
         | 
| 1325 | 
            -
                        Dictionary of extracted metadata
         | 
| 1326 | 
            -
                    """
         | 
| 1327 | 
            -
                    metadata = {}
         | 
| 1328 | 
            -
                    lines = template_content.split('\n')
         | 
| 1329 | 
            -
                    
         | 
| 1330 | 
            -
                    # Extract sections based on the new simplified format
         | 
| 1331 | 
            -
                    current_section = None
         | 
| 1332 | 
            -
                    section_content = []
         | 
| 1333 | 
            -
                    
         | 
| 1334 | 
            -
                    for line in lines:
         | 
| 1335 | 
            -
                        line = line.strip()
         | 
| 1336 | 
            -
                        
         | 
| 1337 | 
            -
                        if line.startswith('## When to Use'):
         | 
| 1338 | 
            -
                            # Save previous section before starting new one
         | 
| 1339 | 
            -
                            if current_section and section_content:
         | 
| 1340 | 
            -
                                metadata[current_section] = section_content.copy()
         | 
| 1341 | 
            -
                            current_section = 'when_to_use'
         | 
| 1342 | 
            -
                            section_content = []
         | 
| 1343 | 
            -
                        elif line.startswith('## Specialized Knowledge'):
         | 
| 1344 | 
            -
                            # Save previous section before starting new one
         | 
| 1345 | 
            -
                            if current_section and section_content:
         | 
| 1346 | 
            -
                                metadata[current_section] = section_content.copy()
         | 
| 1347 | 
            -
                            current_section = 'specialized_knowledge'
         | 
| 1348 | 
            -
                            section_content = []
         | 
| 1349 | 
            -
                        elif line.startswith('## Unique Capabilities'):
         | 
| 1350 | 
            -
                            # Save previous section before starting new one
         | 
| 1351 | 
            -
                            if current_section and section_content:
         | 
| 1352 | 
            -
                                metadata[current_section] = section_content.copy()
         | 
| 1353 | 
            -
                            current_section = 'unique_capabilities'
         | 
| 1354 | 
            -
                            section_content = []
         | 
| 1355 | 
            -
                        elif line.startswith('## ') or line.startswith('# '):
         | 
| 1356 | 
            -
                            # End of section - save current section
         | 
| 1357 | 
            -
                            if current_section and section_content:
         | 
| 1358 | 
            -
                                metadata[current_section] = section_content.copy()
         | 
| 1359 | 
            -
                            current_section = None
         | 
| 1360 | 
            -
                            section_content = []
         | 
| 1361 | 
            -
                        elif current_section and line.startswith('- '):
         | 
| 1362 | 
            -
                            # Extract list item, removing the "- " prefix
         | 
| 1363 | 
            -
                            item = line[2:].strip()
         | 
| 1364 | 
            -
                            if item:
         | 
| 1365 | 
            -
                                section_content.append(item)
         | 
| 1366 | 
            -
                    
         | 
| 1367 | 
            -
                    # Handle last section if file ends without another header
         | 
| 1368 | 
            -
                    if current_section and section_content:
         | 
| 1369 | 
            -
                        metadata[current_section] = section_content.copy()
         | 
| 1370 | 
            -
                    
         | 
| 1371 | 
            -
                    # Ensure all required fields have defaults
         | 
| 1372 | 
            -
                    metadata.setdefault('when_to_use', [])
         | 
| 1373 | 
            -
                    metadata.setdefault('specialized_knowledge', [])
         | 
| 1374 | 
            -
                    metadata.setdefault('unique_capabilities', [])
         | 
| 1375 | 
            -
                    
         | 
| 1376 | 
            -
                    return metadata
         | 
| 1377 | 
            -
                
         | 
| 503 | 
            +
                    return self.filesystem_manager.clean_deployment(config_dir)
         | 
| 504 | 
            +
             | 
| 1378 505 | 
             
                def _get_agent_tools(self, agent_name: str, metadata: Dict[str, Any]) -> List[str]:
         | 
| 1379 | 
            -
                    """
         | 
| 1380 | 
            -
                     | 
| 1381 | 
            -
             | 
| 1382 | 
            -
                     | 
| 1383 | 
            -
             | 
| 1384 | 
            -
                        metadata: Agent metadata
         | 
| 1385 | 
            -
                        
         | 
| 1386 | 
            -
                    Returns:
         | 
| 1387 | 
            -
                        List of tool names
         | 
| 1388 | 
            -
                    """
         | 
| 1389 | 
            -
                    # Base tools all agents should have
         | 
| 1390 | 
            -
                    base_tools = [
         | 
| 1391 | 
            -
                        "Read",
         | 
| 1392 | 
            -
                        "Write", 
         | 
| 1393 | 
            -
                        "Edit",
         | 
| 1394 | 
            -
                        "MultiEdit",
         | 
| 1395 | 
            -
                        "Grep",
         | 
| 1396 | 
            -
                        "Glob",
         | 
| 1397 | 
            -
                        "LS",
         | 
| 1398 | 
            -
                        "TodoWrite"
         | 
| 1399 | 
            -
                    ]
         | 
| 1400 | 
            -
                    
         | 
| 1401 | 
            -
                    # Agent-specific tools
         | 
| 1402 | 
            -
                    agent_tools = {
         | 
| 1403 | 
            -
                        'engineer': base_tools + ["Bash", "WebSearch", "WebFetch"],
         | 
| 1404 | 
            -
                        'qa': base_tools + ["Bash", "WebSearch"],
         | 
| 1405 | 
            -
                        'documentation': base_tools + ["WebSearch", "WebFetch"],
         | 
| 1406 | 
            -
                        'research': base_tools + ["WebSearch", "WebFetch", "Bash"],
         | 
| 1407 | 
            -
                        'security': base_tools + ["Bash", "WebSearch", "Grep"],
         | 
| 1408 | 
            -
                        'ops': base_tools + ["Bash", "WebSearch"],
         | 
| 1409 | 
            -
                        'data_engineer': base_tools + ["Bash", "WebSearch"],
         | 
| 1410 | 
            -
                        'version_control': base_tools + ["Bash"]
         | 
| 1411 | 
            -
                    }
         | 
| 1412 | 
            -
                    
         | 
| 1413 | 
            -
                    # Return specific tools or default set
         | 
| 1414 | 
            -
                    return agent_tools.get(agent_name, base_tools + ["Bash", "WebSearch"])
         | 
| 1415 | 
            -
                
         | 
| 1416 | 
            -
                def _format_version_display(self, version_tuple: tuple) -> str:
         | 
| 1417 | 
            -
                    """
         | 
| 1418 | 
            -
                    Format version tuple for display.
         | 
| 1419 | 
            -
                    
         | 
| 1420 | 
            -
                    Args:
         | 
| 1421 | 
            -
                        version_tuple: Tuple of (major, minor, patch)
         | 
| 1422 | 
            -
                        
         | 
| 1423 | 
            -
                    Returns:
         | 
| 1424 | 
            -
                        Formatted version string
         | 
| 1425 | 
            -
                    """
         | 
| 1426 | 
            -
                    if isinstance(version_tuple, tuple) and len(version_tuple) == 3:
         | 
| 1427 | 
            -
                        major, minor, patch = version_tuple
         | 
| 1428 | 
            -
                        return f"{major}.{minor}.{patch}"
         | 
| 1429 | 
            -
                    else:
         | 
| 1430 | 
            -
                        # Fallback for legacy format
         | 
| 1431 | 
            -
                        return str(version_tuple)
         | 
| 1432 | 
            -
                
         | 
| 1433 | 
            -
                def _is_old_version_format(self, version_str: str) -> bool:
         | 
| 1434 | 
            -
                    """
         | 
| 1435 | 
            -
                    Check if a version string is in the old serial format.
         | 
| 1436 | 
            -
                    
         | 
| 1437 | 
            -
                    Old formats include:
         | 
| 1438 | 
            -
                    - Serial format: "0002-0005" (contains hyphen, all digits)
         | 
| 1439 | 
            -
                    - Missing version field
         | 
| 1440 | 
            -
                    - Non-semantic version formats
         | 
| 1441 | 
            -
                    
         | 
| 1442 | 
            -
                    Args:
         | 
| 1443 | 
            -
                        version_str: Version string to check
         | 
| 1444 | 
            -
                        
         | 
| 1445 | 
            -
                    Returns:
         | 
| 1446 | 
            -
                        True if old format, False if semantic version
         | 
| 1447 | 
            -
                    """
         | 
| 1448 | 
            -
                    if not version_str:
         | 
| 1449 | 
            -
                        return True
         | 
| 1450 | 
            -
                        
         | 
| 1451 | 
            -
                    import re
         | 
| 1452 | 
            -
                    
         | 
| 1453 | 
            -
                    # Check for serial format (e.g., "0002-0005")
         | 
| 1454 | 
            -
                    if re.match(r'^\d+-\d+$', version_str):
         | 
| 1455 | 
            -
                        return True
         | 
| 1456 | 
            -
                        
         | 
| 1457 | 
            -
                    # Check for semantic version format (e.g., "2.1.0")
         | 
| 1458 | 
            -
                    if re.match(r'^v?\d+\.\d+\.\d+$', version_str):
         | 
| 1459 | 
            -
                        return False
         | 
| 1460 | 
            -
                        
         | 
| 1461 | 
            -
                    # Any other format is considered old
         | 
| 1462 | 
            -
                    return True
         | 
| 1463 | 
            -
                
         | 
| 1464 | 
            -
                def _parse_version(self, version_value: Any) -> tuple:
         | 
| 1465 | 
            -
                    """
         | 
| 1466 | 
            -
                    Parse version from various formats to semantic version tuple.
         | 
| 1467 | 
            -
                    
         | 
| 1468 | 
            -
                    Handles:
         | 
| 1469 | 
            -
                    - Integer values: 5 -> (0, 5, 0)
         | 
| 1470 | 
            -
                    - String integers: "5" -> (0, 5, 0)
         | 
| 1471 | 
            -
                    - Semantic versions: "2.1.0" -> (2, 1, 0)
         | 
| 1472 | 
            -
                    - Invalid formats: returns (0, 0, 0)
         | 
| 1473 | 
            -
                    
         | 
| 1474 | 
            -
                    Args:
         | 
| 1475 | 
            -
                        version_value: Version in various formats
         | 
| 1476 | 
            -
                        
         | 
| 1477 | 
            -
                    Returns:
         | 
| 1478 | 
            -
                        Tuple of (major, minor, patch) for comparison
         | 
| 1479 | 
            -
                    """
         | 
| 1480 | 
            -
                    if isinstance(version_value, int):
         | 
| 1481 | 
            -
                        # Legacy integer version - treat as minor version
         | 
| 1482 | 
            -
                        return (0, version_value, 0)
         | 
| 1483 | 
            -
                        
         | 
| 1484 | 
            -
                    if isinstance(version_value, str):
         | 
| 1485 | 
            -
                        # Try to parse as simple integer
         | 
| 1486 | 
            -
                        if version_value.isdigit():
         | 
| 1487 | 
            -
                            return (0, int(version_value), 0)
         | 
| 1488 | 
            -
                        
         | 
| 1489 | 
            -
                        # Try to parse semantic version (e.g., "2.1.0" or "v2.1.0")
         | 
| 1490 | 
            -
                        import re
         | 
| 1491 | 
            -
                        sem_ver_match = re.match(r'^v?(\d+)\.(\d+)\.(\d+)', version_value)
         | 
| 1492 | 
            -
                        if sem_ver_match:
         | 
| 1493 | 
            -
                            major = int(sem_ver_match.group(1))
         | 
| 1494 | 
            -
                            minor = int(sem_ver_match.group(2))
         | 
| 1495 | 
            -
                            patch = int(sem_ver_match.group(3))
         | 
| 1496 | 
            -
                            return (major, minor, patch)
         | 
| 1497 | 
            -
                        
         | 
| 1498 | 
            -
                        # Try to extract first number from string as minor version
         | 
| 1499 | 
            -
                        num_match = re.search(r'(\d+)', version_value)
         | 
| 1500 | 
            -
                        if num_match:
         | 
| 1501 | 
            -
                            return (0, int(num_match.group(1)), 0)
         | 
| 1502 | 
            -
                    
         | 
| 1503 | 
            -
                    # Default to 0.0.0 for invalid formats
         | 
| 1504 | 
            -
                    return (0, 0, 0)
         | 
| 1505 | 
            -
                
         | 
| 1506 | 
            -
                def _format_yaml_list(self, items: List[str], indent: int) -> str:
         | 
| 1507 | 
            -
                    """
         | 
| 1508 | 
            -
                    Format a list for YAML with proper indentation.
         | 
| 1509 | 
            -
                    
         | 
| 1510 | 
            -
                    Args:
         | 
| 1511 | 
            -
                        items: List of items
         | 
| 1512 | 
            -
                        indent: Number of spaces to indent
         | 
| 1513 | 
            -
                        
         | 
| 1514 | 
            -
                    Returns:
         | 
| 1515 | 
            -
                        Formatted YAML list string
         | 
| 1516 | 
            -
                    """
         | 
| 1517 | 
            -
                    if not items:
         | 
| 1518 | 
            -
                        items = ["No items specified"]
         | 
| 1519 | 
            -
                    
         | 
| 1520 | 
            -
                    indent_str = " " * indent
         | 
| 1521 | 
            -
                    formatted_items = []
         | 
| 1522 | 
            -
                    
         | 
| 1523 | 
            -
                    for item in items:
         | 
| 1524 | 
            -
                        # Escape quotes in the item
         | 
| 1525 | 
            -
                        item = item.replace('"', '\\"')
         | 
| 1526 | 
            -
                        formatted_items.append(f'{indent_str}- "{item}"')
         | 
| 1527 | 
            -
                    
         | 
| 1528 | 
            -
                    return '\n'.join(formatted_items)
         | 
| 1529 | 
            -
                
         | 
| 506 | 
            +
                    """Get appropriate tools for an agent based on its type."""
         | 
| 507 | 
            +
                    from .agent_config_provider import AgentConfigProvider
         | 
| 508 | 
            +
             | 
| 509 | 
            +
                    return AgentConfigProvider.get_agent_tools(agent_name, metadata)
         | 
| 510 | 
            +
             | 
| 1530 511 | 
             
                def _get_agent_specific_config(self, agent_name: str) -> Dict[str, Any]:
         | 
| 1531 | 
            -
                    """
         | 
| 1532 | 
            -
                     | 
| 1533 | 
            -
             | 
| 1534 | 
            -
                     | 
| 1535 | 
            -
             | 
| 1536 | 
            -
             | 
| 1537 | 
            -
                     | 
| 1538 | 
            -
             | 
| 1539 | 
            -
                    """
         | 
| 1540 | 
            -
                     | 
| 1541 | 
            -
             | 
| 1542 | 
            -
             | 
| 1543 | 
            -
             | 
| 1544 | 
            -
                         | 
| 1545 | 
            -
             | 
| 1546 | 
            -
             | 
| 1547 | 
            -
                    }
         | 
| 1548 | 
            -
                    
         | 
| 1549 | 
            -
                    # Agent-specific configurations
         | 
| 1550 | 
            -
                    configs = {
         | 
| 1551 | 
            -
                        'engineer': {
         | 
| 1552 | 
            -
                            **base_config,
         | 
| 1553 | 
            -
                            'description': 'Code implementation, development, and inline documentation',
         | 
| 1554 | 
            -
                            'tags': '["engineer", "development", "coding", "implementation"]',
         | 
| 1555 | 
            -
                            'tools': '["Read", "Write", "Edit", "MultiEdit", "Bash", "Grep", "Glob", "LS", "WebSearch", "TodoWrite"]',
         | 
| 1556 | 
            -
                            'temperature': 0.2,
         | 
| 1557 | 
            -
                            'when_to_use': ['Code implementation needed', 'Bug fixes required', 'Refactoring tasks'],
         | 
| 1558 | 
            -
                            'specialized_knowledge': ['Programming best practices', 'Design patterns', 'Code optimization'],
         | 
| 1559 | 
            -
                            'unique_capabilities': ['Write production code', 'Debug complex issues', 'Refactor codebases'],
         | 
| 1560 | 
            -
                            'primary_role': 'Code implementation and development',
         | 
| 1561 | 
            -
                            'specializations': '["coding", "debugging", "refactoring", "optimization"]',
         | 
| 1562 | 
            -
                            'authority': 'ALL code implementation decisions',
         | 
| 1563 | 
            -
                        },
         | 
| 1564 | 
            -
                        'qa': {
         | 
| 1565 | 
            -
                            **base_config,
         | 
| 1566 | 
            -
                            'description': 'Quality assurance, testing, and validation',
         | 
| 1567 | 
            -
                            'tags': '["qa", "testing", "quality", "validation"]',
         | 
| 1568 | 
            -
                            'tools': '["Read", "Write", "Edit", "Bash", "Grep", "Glob", "LS", "TodoWrite"]',
         | 
| 1569 | 
            -
                            'temperature': 0.1,
         | 
| 1570 | 
            -
                            'when_to_use': ['Testing needed', 'Quality validation', 'Test coverage analysis'],
         | 
| 1571 | 
            -
                            'specialized_knowledge': ['Testing methodologies', 'Quality metrics', 'Test automation'],
         | 
| 1572 | 
            -
                            'unique_capabilities': ['Execute test suites', 'Identify edge cases', 'Validate quality'],
         | 
| 1573 | 
            -
                            'primary_role': 'Testing and quality assurance',
         | 
| 1574 | 
            -
                            'specializations': '["testing", "validation", "quality-assurance", "coverage"]',
         | 
| 1575 | 
            -
                            'authority': 'ALL testing and quality decisions',
         | 
| 1576 | 
            -
                        },
         | 
| 1577 | 
            -
                        'documentation': {
         | 
| 1578 | 
            -
                            **base_config,
         | 
| 1579 | 
            -
                            'description': 'Documentation creation, maintenance, and changelog generation',
         | 
| 1580 | 
            -
                            'tags': '["documentation", "writing", "changelog", "docs"]',
         | 
| 1581 | 
            -
                            'tools': '["Read", "Write", "Edit", "MultiEdit", "Grep", "Glob", "LS", "WebSearch", "TodoWrite"]',
         | 
| 1582 | 
            -
                            'temperature': 0.3,
         | 
| 1583 | 
            -
                            'when_to_use': ['Documentation updates needed', 'Changelog generation', 'README updates'],
         | 
| 1584 | 
            -
                            'specialized_knowledge': ['Technical writing', 'Documentation standards', 'Semantic versioning'],
         | 
| 1585 | 
            -
                            'unique_capabilities': ['Create clear documentation', 'Generate changelogs', 'Maintain docs'],
         | 
| 1586 | 
            -
                            'primary_role': 'Documentation and technical writing',
         | 
| 1587 | 
            -
                            'specializations': '["technical-writing", "changelog", "api-docs", "guides"]',
         | 
| 1588 | 
            -
                            'authority': 'ALL documentation decisions',
         | 
| 1589 | 
            -
                        },
         | 
| 1590 | 
            -
                        'research': {
         | 
| 1591 | 
            -
                            **base_config,
         | 
| 1592 | 
            -
                            'description': 'Technical research, analysis, and investigation',
         | 
| 1593 | 
            -
                            'tags': '["research", "analysis", "investigation", "evaluation"]',
         | 
| 1594 | 
            -
                            'tools': '["Read", "Grep", "Glob", "LS", "WebSearch", "WebFetch", "TodoWrite"]',
         | 
| 1595 | 
            -
                            'temperature': 0.4,
         | 
| 1596 | 
            -
                            'when_to_use': ['Technical research needed', 'Solution evaluation', 'Best practices investigation'],
         | 
| 1597 | 
            -
                            'specialized_knowledge': ['Research methodologies', 'Technical analysis', 'Evaluation frameworks'],
         | 
| 1598 | 
            -
                            'unique_capabilities': ['Deep investigation', 'Comparative analysis', 'Evidence-based recommendations'],
         | 
| 1599 | 
            -
                            'primary_role': 'Research and technical analysis',
         | 
| 1600 | 
            -
                            'specializations': '["investigation", "analysis", "evaluation", "recommendations"]',
         | 
| 1601 | 
            -
                            'authority': 'ALL research decisions',
         | 
| 1602 | 
            -
                        },
         | 
| 1603 | 
            -
                        'security': {
         | 
| 1604 | 
            -
                            **base_config,
         | 
| 1605 | 
            -
                            'description': 'Security analysis, vulnerability assessment, and protection',
         | 
| 1606 | 
            -
                            'tags': '["security", "vulnerability", "protection", "audit"]',
         | 
| 1607 | 
            -
                            'tools': '["Read", "Grep", "Glob", "LS", "Bash", "WebSearch", "TodoWrite"]',
         | 
| 1608 | 
            -
                            'temperature': 0.1,
         | 
| 1609 | 
            -
                            'when_to_use': ['Security review needed', 'Vulnerability assessment', 'Security audit'],
         | 
| 1610 | 
            -
                            'specialized_knowledge': ['Security best practices', 'OWASP guidelines', 'Vulnerability patterns'],
         | 
| 1611 | 
            -
                            'unique_capabilities': ['Identify vulnerabilities', 'Security auditing', 'Threat modeling'],
         | 
| 1612 | 
            -
                            'primary_role': 'Security analysis and protection',
         | 
| 1613 | 
            -
                            'specializations': '["vulnerability-assessment", "security-audit", "threat-modeling", "protection"]',
         | 
| 1614 | 
            -
                            'authority': 'ALL security decisions',
         | 
| 1615 | 
            -
                        },
         | 
| 1616 | 
            -
                        'ops': {
         | 
| 1617 | 
            -
                            **base_config,
         | 
| 1618 | 
            -
                            'description': 'Deployment, operations, and infrastructure management',
         | 
| 1619 | 
            -
                            'tags': '["ops", "deployment", "infrastructure", "devops"]',
         | 
| 1620 | 
            -
                            'tools': '["Read", "Write", "Edit", "Bash", "Grep", "Glob", "LS", "TodoWrite"]',
         | 
| 1621 | 
            -
                            'temperature': 0.2,
         | 
| 1622 | 
            -
                            'when_to_use': ['Deployment configuration', 'Infrastructure setup', 'CI/CD pipeline work'],
         | 
| 1623 | 
            -
                            'specialized_knowledge': ['Deployment best practices', 'Infrastructure as code', 'CI/CD'],
         | 
| 1624 | 
            -
                            'unique_capabilities': ['Configure deployments', 'Manage infrastructure', 'Automate operations'],
         | 
| 1625 | 
            -
                            'primary_role': 'Operations and deployment management',
         | 
| 1626 | 
            -
                            'specializations': '["deployment", "infrastructure", "automation", "monitoring"]',
         | 
| 1627 | 
            -
                            'authority': 'ALL operations decisions',
         | 
| 1628 | 
            -
                        },
         | 
| 1629 | 
            -
                        'data_engineer': {
         | 
| 1630 | 
            -
                            **base_config,
         | 
| 1631 | 
            -
                            'description': 'Data pipeline management and AI API integrations',
         | 
| 1632 | 
            -
                            'tags': '["data", "pipeline", "etl", "ai-integration"]',
         | 
| 1633 | 
            -
                            'tools': '["Read", "Write", "Edit", "Bash", "Grep", "Glob", "LS", "WebSearch", "TodoWrite"]',
         | 
| 1634 | 
            -
                            'temperature': 0.2,
         | 
| 1635 | 
            -
                            'when_to_use': ['Data pipeline setup', 'Database design', 'AI API integration'],
         | 
| 1636 | 
            -
                            'specialized_knowledge': ['Data architectures', 'ETL processes', 'AI/ML APIs'],
         | 
| 1637 | 
            -
                            'unique_capabilities': ['Design data schemas', 'Build pipelines', 'Integrate AI services'],
         | 
| 1638 | 
            -
                            'primary_role': 'Data engineering and AI integration',
         | 
| 1639 | 
            -
                            'specializations': '["data-pipelines", "etl", "database", "ai-integration"]',
         | 
| 1640 | 
            -
                            'authority': 'ALL data engineering decisions',
         | 
| 1641 | 
            -
                        },
         | 
| 1642 | 
            -
                        'version_control': {
         | 
| 1643 | 
            -
                            **base_config,
         | 
| 1644 | 
            -
                            'description': 'Git operations, version management, and release coordination',
         | 
| 1645 | 
            -
                            'tags': '["git", "version-control", "release", "branching"]',
         | 
| 1646 | 
            -
                            'tools': '["Read", "Bash", "Grep", "Glob", "LS", "TodoWrite"]',
         | 
| 1647 | 
            -
                            'temperature': 0.1,
         | 
| 1648 | 
            -
                            'network_access': False,  # Git operations are local
         | 
| 1649 | 
            -
                            'when_to_use': ['Git operations needed', 'Version bumping', 'Release management'],
         | 
| 1650 | 
            -
                            'specialized_knowledge': ['Git workflows', 'Semantic versioning', 'Release processes'],
         | 
| 1651 | 
            -
                            'unique_capabilities': ['Complex git operations', 'Version management', 'Release coordination'],
         | 
| 1652 | 
            -
                            'primary_role': 'Version control and release management',
         | 
| 1653 | 
            -
                            'specializations': '["git", "versioning", "branching", "releases"]',
         | 
| 1654 | 
            -
                            'authority': 'ALL version control decisions',
         | 
| 1655 | 
            -
                        }
         | 
| 1656 | 
            -
                    }
         | 
| 1657 | 
            -
                    
         | 
| 1658 | 
            -
                    # Return the specific config or a default
         | 
| 1659 | 
            -
                    return configs.get(agent_name, {
         | 
| 1660 | 
            -
                        **base_config,
         | 
| 1661 | 
            -
                        'description': f'{agent_name.title()} agent for specialized tasks',
         | 
| 1662 | 
            -
                        'tags': f'["{agent_name}", "specialized", "mpm"]',
         | 
| 1663 | 
            -
                        'tools': '["Read", "Write", "Edit", "Grep", "Glob", "LS", "TodoWrite"]',
         | 
| 1664 | 
            -
                        'temperature': 0.3,
         | 
| 1665 | 
            -
                        'when_to_use': [f'When {agent_name} expertise is needed'],
         | 
| 1666 | 
            -
                        'specialized_knowledge': [f'{agent_name.title()} domain knowledge'],
         | 
| 1667 | 
            -
                        'unique_capabilities': [f'{agent_name.title()} specialized operations'],
         | 
| 1668 | 
            -
                        'primary_role': f'{agent_name.title()} operations',
         | 
| 1669 | 
            -
                        'specializations': f'["{agent_name}"]',
         | 
| 1670 | 
            -
                        'authority': f'ALL {agent_name} decisions',
         | 
| 1671 | 
            -
                    })
         | 
| 1672 | 
            -
             | 
| 1673 | 
            -
                def _deploy_system_instructions(self, target_dir: Path, force_rebuild: bool, results: Dict[str, Any]) -> None:
         | 
| 1674 | 
            -
                    """
         | 
| 1675 | 
            -
                    Deploy system instructions and framework files for PM framework.
         | 
| 1676 | 
            -
                    
         | 
| 1677 | 
            -
                    Deploys INSTRUCTIONS.md, WORKFLOW.md, and MEMORY.md files following hierarchy:
         | 
| 1678 | 
            -
                    - System/User versions → Deploy to ~/.claude/
         | 
| 1679 | 
            -
                    - Project-specific versions → Deploy to <project>/.claude/
         | 
| 1680 | 
            -
                    
         | 
| 1681 | 
            -
                    Args:
         | 
| 1682 | 
            -
                        target_dir: Target directory for deployment
         | 
| 1683 | 
            -
                        force_rebuild: Force rebuild even if exists
         | 
| 1684 | 
            -
                        results: Results dictionary to update
         | 
| 1685 | 
            -
                    """
         | 
| 1686 | 
            -
                    try:
         | 
| 1687 | 
            -
                        # Determine target location based on deployment type
         | 
| 1688 | 
            -
                        if self._is_project_specific_deployment():
         | 
| 1689 | 
            -
                            # Project-specific files go to project's .claude directory
         | 
| 1690 | 
            -
                            claude_dir = self.working_directory / ".claude"
         | 
| 1691 | 
            -
                        else:
         | 
| 1692 | 
            -
                            # System and user files go to home ~/.claude directory
         | 
| 1693 | 
            -
                            claude_dir = Path.home() / ".claude"
         | 
| 1694 | 
            -
                        
         | 
| 1695 | 
            -
                        # Ensure .claude directory exists
         | 
| 1696 | 
            -
                        claude_dir.mkdir(parents=True, exist_ok=True)
         | 
| 1697 | 
            -
                        
         | 
| 1698 | 
            -
                        # Framework files to deploy
         | 
| 1699 | 
            -
                        framework_files = [
         | 
| 1700 | 
            -
                            ("INSTRUCTIONS.md", "CLAUDE.md"),  # INSTRUCTIONS.md deploys as CLAUDE.md
         | 
| 1701 | 
            -
                            ("WORKFLOW.md", "WORKFLOW.md"),
         | 
| 1702 | 
            -
                            ("MEMORY.md", "MEMORY.md")
         | 
| 1703 | 
            -
                        ]
         | 
| 1704 | 
            -
                        
         | 
| 1705 | 
            -
                        # Find the agents directory with framework files
         | 
| 1706 | 
            -
                        # Use centralized paths for consistency
         | 
| 1707 | 
            -
                        from claude_mpm.config.paths import paths
         | 
| 1708 | 
            -
                        agents_path = paths.agents_dir
         | 
| 1709 | 
            -
                        
         | 
| 1710 | 
            -
                        for source_name, target_name in framework_files:
         | 
| 1711 | 
            -
                            source_path = agents_path / source_name
         | 
| 1712 | 
            -
                            
         | 
| 1713 | 
            -
                            if not source_path.exists():
         | 
| 1714 | 
            -
                                self.logger.warning(f"Framework file not found: {source_path}")
         | 
| 1715 | 
            -
                                continue
         | 
| 1716 | 
            -
                            
         | 
| 1717 | 
            -
                            target_file = claude_dir / target_name
         | 
| 1718 | 
            -
                            
         | 
| 1719 | 
            -
                            # Check if update needed
         | 
| 1720 | 
            -
                            if not force_rebuild and target_file.exists():
         | 
| 1721 | 
            -
                                # Compare modification times
         | 
| 1722 | 
            -
                                if target_file.stat().st_mtime >= source_path.stat().st_mtime:
         | 
| 1723 | 
            -
                                    results["skipped"].append(target_name)
         | 
| 1724 | 
            -
                                    self.logger.debug(f"Framework file {target_name} up to date")
         | 
| 1725 | 
            -
                                    continue
         | 
| 1726 | 
            -
                            
         | 
| 1727 | 
            -
                            # Read and deploy framework file
         | 
| 1728 | 
            -
                            file_content = source_path.read_text()
         | 
| 1729 | 
            -
                            target_file.write_text(file_content)
         | 
| 1730 | 
            -
                            
         | 
| 1731 | 
            -
                            # Track deployment
         | 
| 1732 | 
            -
                            file_existed = target_file.exists()
         | 
| 1733 | 
            -
                            deployment_info = {
         | 
| 1734 | 
            -
                                "name": target_name,
         | 
| 1735 | 
            -
                                "template": str(source_path),
         | 
| 1736 | 
            -
                                "target": str(target_file)
         | 
| 1737 | 
            -
                            }
         | 
| 1738 | 
            -
                            
         | 
| 1739 | 
            -
                            if file_existed:
         | 
| 1740 | 
            -
                                results["updated"].append(deployment_info)
         | 
| 1741 | 
            -
                                self.logger.info(f"Updated framework file: {target_name}")
         | 
| 1742 | 
            -
                            else:
         | 
| 1743 | 
            -
                                results["deployed"].append(deployment_info)
         | 
| 1744 | 
            -
                                self.logger.info(f"Deployed framework file: {target_name}")
         | 
| 1745 | 
            -
                            
         | 
| 1746 | 
            -
                    except Exception as e:
         | 
| 1747 | 
            -
                        error_msg = f"Failed to deploy system instructions: {e}"
         | 
| 1748 | 
            -
                        self.logger.error(error_msg)
         | 
| 1749 | 
            -
                        results["errors"].append(error_msg)
         | 
| 1750 | 
            -
                        # Not raising AgentDeploymentError as this is non-critical
         | 
| 1751 | 
            -
                
         | 
| 512 | 
            +
                    """Get agent-specific configuration based on agent type."""
         | 
| 513 | 
            +
                    from .agent_config_provider import AgentConfigProvider
         | 
| 514 | 
            +
             | 
| 515 | 
            +
                    return AgentConfigProvider.get_agent_specific_config(agent_name)
         | 
| 516 | 
            +
             | 
| 517 | 
            +
                def _deploy_system_instructions(
         | 
| 518 | 
            +
                    self, target_dir: Path, force_rebuild: bool, results: Dict[str, Any]
         | 
| 519 | 
            +
                ) -> None:
         | 
| 520 | 
            +
                    """Deploy system instructions and framework files for PM framework."""
         | 
| 521 | 
            +
                    from .system_instructions_deployer import SystemInstructionsDeployer
         | 
| 522 | 
            +
             | 
| 523 | 
            +
                    deployer = SystemInstructionsDeployer(self.logger, self.working_directory)
         | 
| 524 | 
            +
                    deployer.deploy_system_instructions(
         | 
| 525 | 
            +
                        target_dir, force_rebuild, results, self._is_project_specific_deployment()
         | 
| 526 | 
            +
                    )
         | 
| 527 | 
            +
             | 
| 1752 528 | 
             
                def _convert_yaml_to_md(self, target_dir: Path) -> Dict[str, Any]:
         | 
| 1753 | 
            -
                    """
         | 
| 1754 | 
            -
                     | 
| 1755 | 
            -
                    
         | 
| 1756 | 
            -
                    This method handles backward compatibility by finding existing .yaml
         | 
| 1757 | 
            -
                    agent files and converting them to .md format expected by Claude Code.
         | 
| 1758 | 
            -
                    
         | 
| 1759 | 
            -
                    Args:
         | 
| 1760 | 
            -
                        target_dir: Directory containing agent files
         | 
| 1761 | 
            -
                        
         | 
| 1762 | 
            -
                    Returns:
         | 
| 1763 | 
            -
                        Dictionary with conversion results:
         | 
| 1764 | 
            -
                        - converted: List of converted files
         | 
| 1765 | 
            -
                        - errors: List of conversion errors
         | 
| 1766 | 
            -
                        - skipped: List of files that didn't need conversion
         | 
| 1767 | 
            -
                    """
         | 
| 1768 | 
            -
                    results = {
         | 
| 1769 | 
            -
                        "converted": [],
         | 
| 1770 | 
            -
                        "errors": [],
         | 
| 1771 | 
            -
                        "skipped": []
         | 
| 1772 | 
            -
                    }
         | 
| 1773 | 
            -
                    
         | 
| 1774 | 
            -
                    try:
         | 
| 1775 | 
            -
                        # Find existing YAML agent files
         | 
| 1776 | 
            -
                        yaml_files = list(target_dir.glob("*.yaml"))
         | 
| 1777 | 
            -
                        
         | 
| 1778 | 
            -
                        if not yaml_files:
         | 
| 1779 | 
            -
                            self.logger.debug("No YAML files found to convert")
         | 
| 1780 | 
            -
                            return results
         | 
| 1781 | 
            -
                        
         | 
| 1782 | 
            -
                        self.logger.info(f"Found {len(yaml_files)} YAML files to convert to MD format")
         | 
| 1783 | 
            -
                        
         | 
| 1784 | 
            -
                        for yaml_file in yaml_files:
         | 
| 1785 | 
            -
                            try:
         | 
| 1786 | 
            -
                                agent_name = yaml_file.stem
         | 
| 1787 | 
            -
                                md_file = target_dir / f"{agent_name}.md"
         | 
| 1788 | 
            -
                                
         | 
| 1789 | 
            -
                                # Skip if MD file already exists (unless it's older than YAML)
         | 
| 1790 | 
            -
                                if md_file.exists():
         | 
| 1791 | 
            -
                                    # Check modification times for safety
         | 
| 1792 | 
            -
                                    yaml_mtime = yaml_file.stat().st_mtime
         | 
| 1793 | 
            -
                                    md_mtime = md_file.stat().st_mtime
         | 
| 1794 | 
            -
                                    
         | 
| 1795 | 
            -
                                    if md_mtime >= yaml_mtime:
         | 
| 1796 | 
            -
                                        results["skipped"].append({
         | 
| 1797 | 
            -
                                            "yaml_file": str(yaml_file),
         | 
| 1798 | 
            -
                                            "md_file": str(md_file),
         | 
| 1799 | 
            -
                                            "reason": "MD file already exists and is newer"
         | 
| 1800 | 
            -
                                        })
         | 
| 1801 | 
            -
                                        continue
         | 
| 1802 | 
            -
                                    else:
         | 
| 1803 | 
            -
                                        # MD file is older, proceed with conversion
         | 
| 1804 | 
            -
                                        self.logger.info(f"MD file {md_file.name} is older than YAML, converting...")
         | 
| 1805 | 
            -
                                
         | 
| 1806 | 
            -
                                # Read YAML content
         | 
| 1807 | 
            -
                                yaml_content = yaml_file.read_text()
         | 
| 1808 | 
            -
                                
         | 
| 1809 | 
            -
                                # Convert YAML to MD with YAML frontmatter
         | 
| 1810 | 
            -
                                md_content = self._convert_yaml_content_to_md(yaml_content, agent_name)
         | 
| 1811 | 
            -
                                
         | 
| 1812 | 
            -
                                # Write MD file
         | 
| 1813 | 
            -
                                md_file.write_text(md_content)
         | 
| 1814 | 
            -
                                
         | 
| 1815 | 
            -
                                # Create backup of YAML file before removing (for safety)
         | 
| 1816 | 
            -
                                backup_file = target_dir / f"{agent_name}.yaml.backup"
         | 
| 1817 | 
            -
                                try:
         | 
| 1818 | 
            -
                                    yaml_file.rename(backup_file)
         | 
| 1819 | 
            -
                                    self.logger.debug(f"Created backup: {backup_file.name}")
         | 
| 1820 | 
            -
                                except Exception as backup_error:
         | 
| 1821 | 
            -
                                    self.logger.warning(f"Failed to create backup for {yaml_file.name}: {backup_error}")
         | 
| 1822 | 
            -
                                    # Still remove the original YAML file even if backup fails
         | 
| 1823 | 
            -
                                    yaml_file.unlink()
         | 
| 1824 | 
            -
                                
         | 
| 1825 | 
            -
                                results["converted"].append({
         | 
| 1826 | 
            -
                                    "from": str(yaml_file),
         | 
| 1827 | 
            -
                                    "to": str(md_file),
         | 
| 1828 | 
            -
                                    "agent": agent_name
         | 
| 1829 | 
            -
                                })
         | 
| 1830 | 
            -
                                
         | 
| 1831 | 
            -
                                self.logger.info(f"Converted {yaml_file.name} to {md_file.name}")
         | 
| 1832 | 
            -
                                
         | 
| 1833 | 
            -
                            except Exception as e:
         | 
| 1834 | 
            -
                                error_msg = f"Failed to convert {yaml_file.name}: {e}"
         | 
| 1835 | 
            -
                                self.logger.error(error_msg)
         | 
| 1836 | 
            -
                                results["errors"].append(error_msg)
         | 
| 1837 | 
            -
                        
         | 
| 1838 | 
            -
                    except Exception as e:
         | 
| 1839 | 
            -
                        error_msg = f"YAML to MD conversion failed: {e}"
         | 
| 1840 | 
            -
                        self.logger.error(error_msg)
         | 
| 1841 | 
            -
                        results["errors"].append(error_msg)
         | 
| 1842 | 
            -
                    
         | 
| 1843 | 
            -
                    return results
         | 
| 1844 | 
            -
                
         | 
| 1845 | 
            -
                def _convert_yaml_content_to_md(self, yaml_content: str, agent_name: str) -> str:
         | 
| 1846 | 
            -
                    """
         | 
| 1847 | 
            -
                    Convert YAML agent content to MD format with YAML frontmatter.
         | 
| 1848 | 
            -
                    
         | 
| 1849 | 
            -
                    Args:
         | 
| 1850 | 
            -
                        yaml_content: Original YAML content
         | 
| 1851 | 
            -
                        agent_name: Name of the agent
         | 
| 1852 | 
            -
                        
         | 
| 1853 | 
            -
                    Returns:
         | 
| 1854 | 
            -
                        Markdown content with YAML frontmatter
         | 
| 1855 | 
            -
                    """
         | 
| 1856 | 
            -
                    import re
         | 
| 1857 | 
            -
                    from datetime import datetime
         | 
| 1858 | 
            -
                    
         | 
| 1859 | 
            -
                    # Extract YAML frontmatter and content
         | 
| 1860 | 
            -
                    yaml_parts = yaml_content.split('---', 2)
         | 
| 1861 | 
            -
                    
         | 
| 1862 | 
            -
                    if len(yaml_parts) < 3:
         | 
| 1863 | 
            -
                        # No proper YAML frontmatter, treat entire content as instructions
         | 
| 1864 | 
            -
                        frontmatter = f"""---
         | 
| 1865 | 
            -
            name: {agent_name}
         | 
| 1866 | 
            -
            description: "Agent for specialized tasks"
         | 
| 1867 | 
            -
            version: "1.0.0"
         | 
| 1868 | 
            -
            author: "claude-mpm@anthropic.com"
         | 
| 1869 | 
            -
            created: "{datetime.now().isoformat()}Z"
         | 
| 1870 | 
            -
            updated: "{datetime.now().isoformat()}Z"
         | 
| 1871 | 
            -
            tags: ["{agent_name}", "mpm-framework"]
         | 
| 1872 | 
            -
            tools: ["Read", "Write", "Edit", "Grep", "Glob", "LS"]
         | 
| 1873 | 
            -
            metadata:
         | 
| 1874 | 
            -
              deployment_type: "system"
         | 
| 1875 | 
            -
              converted_from: "yaml"
         | 
| 1876 | 
            -
            ---
         | 
| 529 | 
            +
                    """Convert existing YAML agent files to MD format with YAML frontmatter."""
         | 
| 530 | 
            +
                    return self.format_converter.convert_yaml_to_md(target_dir)
         | 
| 1877 531 |  | 
| 1878 | 
            -
             | 
| 1879 | 
            -
             | 
| 1880 | 
            -
                    
         | 
| 1881 | 
            -
             | 
| 1882 | 
            -
                     | 
| 1883 | 
            -
                    instructions = yaml_parts[2].strip()
         | 
| 1884 | 
            -
                    
         | 
| 1885 | 
            -
                    # Extract key fields from YAML frontmatter
         | 
| 1886 | 
            -
                    name = agent_name
         | 
| 1887 | 
            -
                    description = self._extract_yaml_field(yaml_frontmatter, 'description') or f"{agent_name.title()} agent for specialized tasks"
         | 
| 1888 | 
            -
                    version = self._extract_yaml_field(yaml_frontmatter, 'version') or "1.0.0"
         | 
| 1889 | 
            -
                    tools_line = self._extract_yaml_field(yaml_frontmatter, 'tools') or "Read, Write, Edit, Grep, Glob, LS"
         | 
| 1890 | 
            -
                    
         | 
| 1891 | 
            -
                    # Convert tools string to list format
         | 
| 1892 | 
            -
                    if isinstance(tools_line, str):
         | 
| 1893 | 
            -
                        if tools_line.startswith('[') and tools_line.endswith(']'):
         | 
| 1894 | 
            -
                            # Already in list format
         | 
| 1895 | 
            -
                            tools_list = tools_line
         | 
| 1896 | 
            -
                        else:
         | 
| 1897 | 
            -
                            # Convert comma-separated to list
         | 
| 1898 | 
            -
                            tools = [tool.strip() for tool in tools_line.split(',')]
         | 
| 1899 | 
            -
                            tools_list = str(tools)
         | 
| 1900 | 
            -
                    else:
         | 
| 1901 | 
            -
                        tools_list = str(tools_line) if tools_line else '["Read", "Write", "Edit", "Grep", "Glob", "LS"]'
         | 
| 1902 | 
            -
                    
         | 
| 1903 | 
            -
                    # Build new YAML frontmatter
         | 
| 1904 | 
            -
                    new_frontmatter = f"""---
         | 
| 1905 | 
            -
            name: {name}
         | 
| 1906 | 
            -
            description: "{description}"
         | 
| 1907 | 
            -
            version: "{version}"
         | 
| 1908 | 
            -
            author: "claude-mpm@anthropic.com"
         | 
| 1909 | 
            -
            created: "{datetime.now().isoformat()}Z"
         | 
| 1910 | 
            -
            updated: "{datetime.now().isoformat()}Z"
         | 
| 1911 | 
            -
            tags: ["{agent_name}", "mpm-framework"]
         | 
| 1912 | 
            -
            tools: {tools_list}
         | 
| 1913 | 
            -
            metadata:
         | 
| 1914 | 
            -
              deployment_type: "system"
         | 
| 1915 | 
            -
              converted_from: "yaml"
         | 
| 1916 | 
            -
            ---
         | 
| 532 | 
            +
                def _convert_yaml_content_to_md(self, yaml_content: str, agent_name: str) -> str:
         | 
| 533 | 
            +
                    """Convert YAML agent content to MD format with YAML frontmatter."""
         | 
| 534 | 
            +
                    return self.format_converter.convert_yaml_content_to_md(
         | 
| 535 | 
            +
                        yaml_content, agent_name
         | 
| 536 | 
            +
                    )
         | 
| 1917 537 |  | 
| 1918 | 
            -
            """
         | 
| 1919 | 
            -
                    
         | 
| 1920 | 
            -
                    return new_frontmatter + instructions
         | 
| 1921 | 
            -
                
         | 
| 1922 538 | 
             
                def _extract_yaml_field(self, yaml_content: str, field_name: str) -> str:
         | 
| 1923 | 
            -
                    """
         | 
| 1924 | 
            -
                     | 
| 1925 | 
            -
             | 
| 1926 | 
            -
             | 
| 1927 | 
            -
             | 
| 1928 | 
            -
             | 
| 1929 | 
            -
             | 
| 1930 | 
            -
                     | 
| 1931 | 
            -
             | 
| 1932 | 
            -
             | 
| 1933 | 
            -
                    import re
         | 
| 1934 | 
            -
                    
         | 
| 1935 | 
            -
                    try:
         | 
| 1936 | 
            -
                        # Match field with quoted or unquoted values
         | 
| 1937 | 
            -
                        pattern = rf'^{field_name}:\s*["\']?(.*?)["\']?\s*$'
         | 
| 1938 | 
            -
                        match = re.search(pattern, yaml_content, re.MULTILINE)
         | 
| 1939 | 
            -
                        
         | 
| 1940 | 
            -
                        if match:
         | 
| 1941 | 
            -
                            return match.group(1).strip()
         | 
| 1942 | 
            -
                        
         | 
| 1943 | 
            -
                        # Try with alternative spacing patterns
         | 
| 1944 | 
            -
                        pattern = rf'^{field_name}\s*:\s*(.+)$'
         | 
| 1945 | 
            -
                        match = re.search(pattern, yaml_content, re.MULTILINE)
         | 
| 1946 | 
            -
                        
         | 
| 1947 | 
            -
                        if match:
         | 
| 1948 | 
            -
                            value = match.group(1).strip()
         | 
| 1949 | 
            -
                            # Remove quotes if present
         | 
| 1950 | 
            -
                            if (value.startswith('"') and value.endswith('"')) or \
         | 
| 1951 | 
            -
                               (value.startswith("'") and value.endswith("'")):
         | 
| 1952 | 
            -
                                value = value[1:-1]
         | 
| 1953 | 
            -
                            return value
         | 
| 1954 | 
            -
                            
         | 
| 1955 | 
            -
                    except Exception as e:
         | 
| 1956 | 
            -
                        self.logger.warning(f"Error extracting YAML field '{field_name}': {e}")
         | 
| 1957 | 
            -
                    
         | 
| 1958 | 
            -
                    return None
         | 
| 1959 | 
            -
                
         | 
| 1960 | 
            -
                def _try_async_deployment(self, target_dir: Optional[Path], force_rebuild: bool, 
         | 
| 1961 | 
            -
                                          config: Optional[Config], deployment_start_time: float) -> Optional[Dict[str, Any]]:
         | 
| 539 | 
            +
                    """Extract a field value from YAML content."""
         | 
| 540 | 
            +
                    return self.format_converter.extract_yaml_field(yaml_content, field_name)
         | 
| 541 | 
            +
             | 
| 542 | 
            +
                def _try_async_deployment(
         | 
| 543 | 
            +
                    self,
         | 
| 544 | 
            +
                    target_dir: Optional[Path],
         | 
| 545 | 
            +
                    force_rebuild: bool,
         | 
| 546 | 
            +
                    config: Optional[Config],
         | 
| 547 | 
            +
                    deployment_start_time: float,
         | 
| 548 | 
            +
                ) -> Optional[Dict[str, Any]]:
         | 
| 1962 549 | 
             
                    """
         | 
| 1963 550 | 
             
                    Try to use async deployment for better performance.
         | 
| 1964 | 
            -
             | 
| 551 | 
            +
             | 
| 1965 552 | 
             
                    WHY: Async deployment is 50-70% faster than synchronous deployment
         | 
| 1966 553 | 
             
                    by using concurrent operations for file I/O and processing.
         | 
| 1967 | 
            -
             | 
| 554 | 
            +
             | 
| 1968 555 | 
             
                    Args:
         | 
| 1969 556 | 
             
                        target_dir: Target directory for deployment
         | 
| 1970 557 | 
             
                        force_rebuild: Whether to force rebuild
         | 
| 1971 558 | 
             
                        config: Configuration object
         | 
| 1972 559 | 
             
                        deployment_start_time: Start time for metrics
         | 
| 1973 | 
            -
             | 
| 560 | 
            +
             | 
| 1974 561 | 
             
                    Returns:
         | 
| 1975 562 | 
             
                        Deployment results if successful, None if async not available
         | 
| 1976 563 | 
             
                    """
         | 
| 1977 564 | 
             
                    try:
         | 
| 1978 565 | 
             
                        from .async_agent_deployment import deploy_agents_async_wrapper
         | 
| 566 | 
            +
             | 
| 1979 567 | 
             
                        self.logger.info("Using async deployment for improved performance")
         | 
| 1980 | 
            -
             | 
| 568 | 
            +
             | 
| 1981 569 | 
             
                        # Run async deployment
         | 
| 1982 570 | 
             
                        results = deploy_agents_async_wrapper(
         | 
| 1983 571 | 
             
                            templates_dir=self.templates_dir,
         | 
| @@ -1985,187 +573,82 @@ metadata: | |
| 1985 573 | 
             
                            working_directory=self.working_directory,
         | 
| 1986 574 | 
             
                            target_dir=target_dir,
         | 
| 1987 575 | 
             
                            force_rebuild=force_rebuild,
         | 
| 1988 | 
            -
                            config=config
         | 
| 576 | 
            +
                            config=config,
         | 
| 1989 577 | 
             
                        )
         | 
| 1990 | 
            -
             | 
| 578 | 
            +
             | 
| 1991 579 | 
             
                        # Add metrics about async vs sync
         | 
| 1992 | 
            -
                        if  | 
| 1993 | 
            -
                            results[ | 
| 1994 | 
            -
                            duration_ms = results[ | 
| 580 | 
            +
                        if "metrics" in results:
         | 
| 581 | 
            +
                            results["metrics"]["deployment_method"] = "async"
         | 
| 582 | 
            +
                            duration_ms = results["metrics"].get("duration_ms", 0)
         | 
| 1995 583 | 
             
                            self.logger.info(f"Async deployment completed in {duration_ms:.1f}ms")
         | 
| 1996 | 
            -
             | 
| 584 | 
            +
             | 
| 1997 585 | 
             
                            # Update internal metrics
         | 
| 1998 | 
            -
                            self._deployment_metrics[ | 
| 1999 | 
            -
                            if not results.get( | 
| 2000 | 
            -
                                self._deployment_metrics[ | 
| 586 | 
            +
                            self._deployment_metrics["total_deployments"] += 1
         | 
| 587 | 
            +
                            if not results.get("errors"):
         | 
| 588 | 
            +
                                self._deployment_metrics["successful_deployments"] += 1
         | 
| 2001 589 | 
             
                            else:
         | 
| 2002 | 
            -
                                self._deployment_metrics[ | 
| 2003 | 
            -
             | 
| 590 | 
            +
                                self._deployment_metrics["failed_deployments"] += 1
         | 
| 591 | 
            +
             | 
| 2004 592 | 
             
                        return results
         | 
| 2005 | 
            -
             | 
| 593 | 
            +
             | 
| 2006 594 | 
             
                    except ImportError:
         | 
| 2007 595 | 
             
                        self.logger.warning("Async deployment not available, falling back to sync")
         | 
| 2008 596 | 
             
                        return None
         | 
| 2009 597 | 
             
                    except Exception as e:
         | 
| 2010 598 | 
             
                        self.logger.warning(f"Async deployment failed, falling back to sync: {e}")
         | 
| 2011 599 | 
             
                        return None
         | 
| 2012 | 
            -
             | 
| 600 | 
            +
             | 
| 2013 601 | 
             
                def _load_deployment_config(self, config: Optional[Config]) -> tuple:
         | 
| 2014 | 
            -
                    """
         | 
| 2015 | 
            -
                     | 
| 2016 | 
            -
             | 
| 2017 | 
            -
                     | 
| 2018 | 
            -
                     | 
| 2019 | 
            -
             | 
| 2020 | 
            -
                    Args:
         | 
| 2021 | 
            -
                        config: Optional configuration object
         | 
| 2022 | 
            -
                        
         | 
| 2023 | 
            -
                    Returns:
         | 
| 2024 | 
            -
                        Tuple of (config, excluded_agents)
         | 
| 2025 | 
            -
                    """
         | 
| 2026 | 
            -
                    # Load configuration if not provided
         | 
| 2027 | 
            -
                    if config is None:
         | 
| 2028 | 
            -
                        config = Config()
         | 
| 2029 | 
            -
                    
         | 
| 2030 | 
            -
                    # Get agent exclusion configuration
         | 
| 2031 | 
            -
                    excluded_agents = config.get('agent_deployment.excluded_agents', [])
         | 
| 2032 | 
            -
                    case_sensitive = config.get('agent_deployment.case_sensitive', False)
         | 
| 2033 | 
            -
                    exclude_dependencies = config.get('agent_deployment.exclude_dependencies', False)
         | 
| 2034 | 
            -
                    
         | 
| 2035 | 
            -
                    # Normalize excluded agents list for comparison
         | 
| 2036 | 
            -
                    if not case_sensitive:
         | 
| 2037 | 
            -
                        excluded_agents = [agent.lower() for agent in excluded_agents]
         | 
| 2038 | 
            -
                    
         | 
| 2039 | 
            -
                    # Log exclusion configuration if agents are being excluded
         | 
| 2040 | 
            -
                    if excluded_agents:
         | 
| 2041 | 
            -
                        self.logger.info(f"Excluding agents from deployment: {excluded_agents}")
         | 
| 2042 | 
            -
                        self.logger.debug(f"Case sensitive matching: {case_sensitive}")
         | 
| 2043 | 
            -
                        self.logger.debug(f"Exclude dependencies: {exclude_dependencies}")
         | 
| 2044 | 
            -
                    
         | 
| 2045 | 
            -
                    return config, excluded_agents
         | 
| 2046 | 
            -
                
         | 
| 602 | 
            +
                    """Load and process deployment configuration."""
         | 
| 603 | 
            +
                    from .deployment_config_loader import DeploymentConfigLoader
         | 
| 604 | 
            +
             | 
| 605 | 
            +
                    loader = DeploymentConfigLoader(self.logger)
         | 
| 606 | 
            +
                    return loader.load_deployment_config(config)
         | 
| 607 | 
            +
             | 
| 2047 608 | 
             
                def _determine_agents_directory(self, target_dir: Optional[Path]) -> Path:
         | 
| 2048 | 
            -
                    """
         | 
| 2049 | 
            -
                     | 
| 2050 | 
            -
             | 
| 2051 | 
            -
                     | 
| 2052 | 
            -
             | 
| 2053 | 
            -
             | 
| 2054 | 
            -
             | 
| 2055 | 
            -
                     | 
| 2056 | 
            -
                     | 
| 2057 | 
            -
             | 
| 2058 | 
            -
                    
         | 
| 2059 | 
            -
                    Args:
         | 
| 2060 | 
            -
                        target_dir: Optional target directory
         | 
| 2061 | 
            -
                        
         | 
| 2062 | 
            -
                    Returns:
         | 
| 2063 | 
            -
                        Path to agents directory
         | 
| 2064 | 
            -
                    """
         | 
| 2065 | 
            -
                    if not target_dir:
         | 
| 2066 | 
            -
                        # Default deployment location depends on agent source
         | 
| 2067 | 
            -
                        # Check if we're deploying system agents or user/project agents
         | 
| 2068 | 
            -
                        if self._is_system_agent_deployment():
         | 
| 2069 | 
            -
                            # System agents go to user's home ~/.claude/agents/
         | 
| 2070 | 
            -
                            return Path.home() / ".claude" / "agents"
         | 
| 2071 | 
            -
                        elif self._is_project_specific_deployment():
         | 
| 2072 | 
            -
                            # Project agents stay in project directory
         | 
| 2073 | 
            -
                            return self.working_directory / ".claude" / "agents"
         | 
| 2074 | 
            -
                        else:
         | 
| 2075 | 
            -
                            # Default: User custom agents go to home ~/.claude/agents/
         | 
| 2076 | 
            -
                            return Path.home() / ".claude" / "agents"
         | 
| 2077 | 
            -
                    
         | 
| 2078 | 
            -
                    # If target_dir provided, use it directly (caller decides structure)
         | 
| 2079 | 
            -
                    target_dir = Path(target_dir)
         | 
| 2080 | 
            -
                    
         | 
| 2081 | 
            -
                    # Check if this is already an agents directory
         | 
| 2082 | 
            -
                    if target_dir.name == "agents":
         | 
| 2083 | 
            -
                        # Already an agents directory, use as-is
         | 
| 2084 | 
            -
                        return target_dir
         | 
| 2085 | 
            -
                    elif target_dir.name == ".claude-mpm":
         | 
| 2086 | 
            -
                        # .claude-mpm directory, add agents subdirectory
         | 
| 2087 | 
            -
                        return target_dir / "agents"
         | 
| 2088 | 
            -
                    elif target_dir.name == ".claude":
         | 
| 2089 | 
            -
                        # .claude directory, add agents subdirectory
         | 
| 2090 | 
            -
                        return target_dir / "agents"
         | 
| 2091 | 
            -
                    else:
         | 
| 2092 | 
            -
                        # Assume it's a project directory, add .claude/agents
         | 
| 2093 | 
            -
                        return target_dir / ".claude" / "agents"
         | 
| 2094 | 
            -
                
         | 
| 609 | 
            +
                    """Determine the correct agents directory based on input."""
         | 
| 610 | 
            +
                    from .agents_directory_resolver import AgentsDirectoryResolver
         | 
| 611 | 
            +
             | 
| 612 | 
            +
                    resolver = AgentsDirectoryResolver(
         | 
| 613 | 
            +
                        self.working_directory,
         | 
| 614 | 
            +
                        self._is_system_agent_deployment(),
         | 
| 615 | 
            +
                        self._is_project_specific_deployment(),
         | 
| 616 | 
            +
                    )
         | 
| 617 | 
            +
                    return resolver.determine_agents_directory(target_dir)
         | 
| 618 | 
            +
             | 
| 2095 619 | 
             
                def _is_system_agent_deployment(self) -> bool:
         | 
| 2096 | 
            -
                    """
         | 
| 2097 | 
            -
                     | 
| 2098 | 
            -
             | 
| 2099 | 
            -
                     | 
| 2100 | 
            -
             | 
| 2101 | 
            -
                    
         | 
| 2102 | 
            -
                    Returns:
         | 
| 2103 | 
            -
                        True if deploying system agents, False otherwise
         | 
| 2104 | 
            -
                    """
         | 
| 2105 | 
            -
                    # Check if templates_dir points to the system templates
         | 
| 2106 | 
            -
                    if self.templates_dir and self.templates_dir.exists():
         | 
| 2107 | 
            -
                        # System agents are in the package's agents/templates directory
         | 
| 2108 | 
            -
                        try:
         | 
| 2109 | 
            -
                            # Check if templates_dir is within the claude_mpm package structure
         | 
| 2110 | 
            -
                            templates_str = str(self.templates_dir.resolve())
         | 
| 2111 | 
            -
                            return ("site-packages/claude_mpm" in templates_str or 
         | 
| 2112 | 
            -
                                    "src/claude_mpm/agents/templates" in templates_str or
         | 
| 2113 | 
            -
                                    (paths.agents_dir / "templates").resolve() == self.templates_dir.resolve())
         | 
| 2114 | 
            -
                        except Exception:
         | 
| 2115 | 
            -
                            pass
         | 
| 2116 | 
            -
                    return False
         | 
| 2117 | 
            -
                
         | 
| 620 | 
            +
                    """Check if this is a deployment of system agents."""
         | 
| 621 | 
            +
                    from .deployment_type_detector import DeploymentTypeDetector
         | 
| 622 | 
            +
             | 
| 623 | 
            +
                    return DeploymentTypeDetector.is_system_agent_deployment(self.templates_dir)
         | 
| 624 | 
            +
             | 
| 2118 625 | 
             
                def _is_project_specific_deployment(self) -> bool:
         | 
| 2119 | 
            -
                    """
         | 
| 2120 | 
            -
                     | 
| 2121 | 
            -
             | 
| 2122 | 
            -
                     | 
| 2123 | 
            -
             | 
| 2124 | 
            -
                    
         | 
| 2125 | 
            -
             | 
| 2126 | 
            -
                        True if deploying project-specific agents, False otherwise
         | 
| 2127 | 
            -
                    """
         | 
| 2128 | 
            -
                    # Check if we're in a project directory with .claude-mpm/agents
         | 
| 2129 | 
            -
                    project_agents_dir = self.working_directory / ".claude-mpm" / "agents"
         | 
| 2130 | 
            -
                    if project_agents_dir.exists():
         | 
| 2131 | 
            -
                        # Check if templates_dir points to project agents
         | 
| 2132 | 
            -
                        if self.templates_dir and self.templates_dir.exists():
         | 
| 2133 | 
            -
                            try:
         | 
| 2134 | 
            -
                                return project_agents_dir.resolve() == self.templates_dir.resolve()
         | 
| 2135 | 
            -
                            except Exception:
         | 
| 2136 | 
            -
                                pass
         | 
| 2137 | 
            -
                    return False
         | 
| 2138 | 
            -
                
         | 
| 626 | 
            +
                    """Check if deploying project-specific agents."""
         | 
| 627 | 
            +
                    from .deployment_type_detector import DeploymentTypeDetector
         | 
| 628 | 
            +
             | 
| 629 | 
            +
                    return DeploymentTypeDetector.is_project_specific_deployment(
         | 
| 630 | 
            +
                        self.templates_dir, self.working_directory
         | 
| 631 | 
            +
                    )
         | 
| 632 | 
            +
             | 
| 2139 633 | 
             
                def _is_user_custom_deployment(self) -> bool:
         | 
| 2140 | 
            -
                    """
         | 
| 2141 | 
            -
                     | 
| 2142 | 
            -
             | 
| 2143 | 
            -
                     | 
| 2144 | 
            -
                    
         | 
| 2145 | 
            -
                    Returns:
         | 
| 2146 | 
            -
                        True if deploying user custom agents, False otherwise
         | 
| 2147 | 
            -
                    """
         | 
| 2148 | 
            -
                    user_agents_dir = Path.home() / ".claude-mpm" / "agents"
         | 
| 2149 | 
            -
                    if user_agents_dir.exists():
         | 
| 2150 | 
            -
                        # Check if templates_dir points to user agents
         | 
| 2151 | 
            -
                        if self.templates_dir and self.templates_dir.exists():
         | 
| 2152 | 
            -
                            try:
         | 
| 2153 | 
            -
                                return user_agents_dir.resolve() == self.templates_dir.resolve()
         | 
| 2154 | 
            -
                            except Exception:
         | 
| 2155 | 
            -
                                pass
         | 
| 2156 | 
            -
                    return False
         | 
| 634 | 
            +
                    """Check if deploying user custom agents."""
         | 
| 635 | 
            +
                    from .deployment_type_detector import DeploymentTypeDetector
         | 
| 636 | 
            +
             | 
| 637 | 
            +
                    return DeploymentTypeDetector.is_user_custom_deployment(self.templates_dir)
         | 
| 2157 638 |  | 
| 2158 | 
            -
                def _initialize_deployment_results( | 
| 639 | 
            +
                def _initialize_deployment_results(
         | 
| 640 | 
            +
                    self, agents_dir: Path, deployment_start_time: float
         | 
| 641 | 
            +
                ) -> Dict[str, Any]:
         | 
| 2159 642 | 
             
                    """
         | 
| 2160 643 | 
             
                    Initialize the deployment results dictionary.
         | 
| 2161 | 
            -
             | 
| 644 | 
            +
             | 
| 2162 645 | 
             
                    WHY: Consistent result structure ensures all deployment
         | 
| 2163 646 | 
             
                    operations return the same format for easier processing.
         | 
| 2164 | 
            -
             | 
| 647 | 
            +
             | 
| 2165 648 | 
             
                    Args:
         | 
| 2166 649 | 
             
                        agents_dir: Target agents directory
         | 
| 2167 650 | 
             
                        deployment_start_time: Start time for metrics
         | 
| 2168 | 
            -
             | 
| 651 | 
            +
             | 
| 2169 652 | 
             
                    Returns:
         | 
| 2170 653 | 
             
                        Initialized results dictionary
         | 
| 2171 654 | 
             
                    """
         | 
| @@ -2186,17 +669,19 @@ metadata: | |
| 2186 669 | 
             
                            "duration_ms": None,
         | 
| 2187 670 | 
             
                            "agent_timings": {},  # Track individual agent deployment times
         | 
| 2188 671 | 
             
                            "validation_times": {},  # Track template validation times
         | 
| 2189 | 
            -
                            "resource_usage": {}  # Could track memory/CPU if needed
         | 
| 2190 | 
            -
                        }
         | 
| 672 | 
            +
                            "resource_usage": {},  # Could track memory/CPU if needed
         | 
| 673 | 
            +
                        },
         | 
| 2191 674 | 
             
                    }
         | 
| 2192 | 
            -
             | 
| 2193 | 
            -
                def _repair_existing_agents( | 
| 675 | 
            +
             | 
| 676 | 
            +
                def _repair_existing_agents(
         | 
| 677 | 
            +
                    self, agents_dir: Path, results: Dict[str, Any]
         | 
| 678 | 
            +
                ) -> None:
         | 
| 2194 679 | 
             
                    """
         | 
| 2195 680 | 
             
                    Validate and repair broken frontmatter in existing agents.
         | 
| 2196 | 
            -
             | 
| 681 | 
            +
             | 
| 2197 682 | 
             
                    WHY: Ensures all existing agents have valid YAML frontmatter
         | 
| 2198 683 | 
             
                    before deployment, preventing runtime errors in Claude Code.
         | 
| 2199 | 
            -
             | 
| 684 | 
            +
             | 
| 2200 685 | 
             
                    Args:
         | 
| 2201 686 | 
             
                        agents_dir: Directory containing agent files
         | 
| 2202 687 | 
             
                        results: Results dictionary to update
         | 
| @@ -2204,124 +689,42 @@ metadata: | |
| 2204 689 | 
             
                    repair_results = self._validate_and_repair_existing_agents(agents_dir)
         | 
| 2205 690 | 
             
                    if repair_results["repaired"]:
         | 
| 2206 691 | 
             
                        results["repaired"] = repair_results["repaired"]
         | 
| 2207 | 
            -
                        self.logger.info( | 
| 692 | 
            +
                        self.logger.info(
         | 
| 693 | 
            +
                            f"Repaired frontmatter in {len(repair_results['repaired'])} existing agents"
         | 
| 694 | 
            +
                        )
         | 
| 2208 695 | 
             
                        for agent_name in repair_results["repaired"]:
         | 
| 2209 696 | 
             
                            self.logger.debug(f"  - Repaired: {agent_name}")
         | 
| 2210 | 
            -
             | 
| 697 | 
            +
             | 
| 2211 698 | 
             
                def _determine_source_tier(self) -> str:
         | 
| 2212 | 
            -
                    """
         | 
| 2213 | 
            -
                     | 
| 2214 | 
            -
             | 
| 2215 | 
            -
                     | 
| 2216 | 
            -
             | 
| 2217 | 
            -
                    
         | 
| 2218 | 
            -
                    Returns:
         | 
| 2219 | 
            -
                        Source tier string
         | 
| 2220 | 
            -
                    """
         | 
| 2221 | 
            -
                    if ".claude-mpm/agents" in str(self.templates_dir) and "/templates" not in str(self.templates_dir):
         | 
| 2222 | 
            -
                        return "PROJECT"
         | 
| 2223 | 
            -
                    elif "/.claude-mpm/agents" in str(self.templates_dir) and "/templates" not in str(self.templates_dir):
         | 
| 2224 | 
            -
                        return "USER"
         | 
| 2225 | 
            -
                    return "SYSTEM"
         | 
| 2226 | 
            -
                
         | 
| 699 | 
            +
                    """Determine the source tier for logging."""
         | 
| 700 | 
            +
                    from .deployment_type_detector import DeploymentTypeDetector
         | 
| 701 | 
            +
             | 
| 702 | 
            +
                    return DeploymentTypeDetector.determine_source_tier(self.templates_dir)
         | 
| 703 | 
            +
             | 
| 2227 704 | 
             
                def _load_base_agent(self) -> tuple:
         | 
| 2228 | 
            -
                    """
         | 
| 2229 | 
            -
                     | 
| 2230 | 
            -
             | 
| 2231 | 
            -
                    WHY: Base agent contains shared configuration that all agents
         | 
| 2232 | 
            -
                    inherit, reducing duplication and ensuring consistency.
         | 
| 2233 | 
            -
                    
         | 
| 2234 | 
            -
                    Returns:
         | 
| 2235 | 
            -
                        Tuple of (base_agent_data, base_agent_version)
         | 
| 2236 | 
            -
                    """
         | 
| 2237 | 
            -
                    base_agent_data = {}
         | 
| 2238 | 
            -
                    base_agent_version = (0, 0, 0)
         | 
| 2239 | 
            -
                    
         | 
| 2240 | 
            -
                    if self.base_agent_path.exists():
         | 
| 2241 | 
            -
                        try:
         | 
| 2242 | 
            -
                            import json
         | 
| 2243 | 
            -
                            base_agent_data = json.loads(self.base_agent_path.read_text())
         | 
| 2244 | 
            -
                            # Handle both 'base_version' (new format) and 'version' (old format)
         | 
| 2245 | 
            -
                            # MIGRATION PATH: Supporting both formats during transition period
         | 
| 2246 | 
            -
                            base_agent_version = self._parse_version(
         | 
| 2247 | 
            -
                                base_agent_data.get('base_version') or base_agent_data.get('version', 0)
         | 
| 2248 | 
            -
                            )
         | 
| 2249 | 
            -
                            self.logger.info(f"Loaded base agent template (version {self._format_version_display(base_agent_version)})")
         | 
| 2250 | 
            -
                        except Exception as e:
         | 
| 2251 | 
            -
                            # NON-FATAL: Base agent is optional enhancement, not required
         | 
| 2252 | 
            -
                            self.logger.warning(f"Could not load base agent: {e}")
         | 
| 2253 | 
            -
                    
         | 
| 2254 | 
            -
                    return base_agent_data, base_agent_version
         | 
| 2255 | 
            -
                
         | 
| 705 | 
            +
                    """Load base agent content and version."""
         | 
| 706 | 
            +
                    return self.configuration_manager.load_base_agent()
         | 
| 707 | 
            +
             | 
| 2256 708 | 
             
                def _get_filtered_templates(self, excluded_agents: list, config: Config) -> list:
         | 
| 2257 | 
            -
                    """
         | 
| 2258 | 
            -
                     | 
| 2259 | 
            -
             | 
| 2260 | 
            -
             | 
| 2261 | 
            -
                     | 
| 2262 | 
            -
                    
         | 
| 2263 | 
            -
                     | 
| 2264 | 
            -
             | 
| 2265 | 
            -
             | 
| 2266 | 
            -
             | 
| 2267 | 
            -
                     | 
| 2268 | 
            -
             | 
| 2269 | 
            -
             | 
| 2270 | 
            -
                    # Get all template files
         | 
| 2271 | 
            -
                    template_files = list(self.templates_dir.glob("*.json"))
         | 
| 2272 | 
            -
                    
         | 
| 2273 | 
            -
                    # Build the combined exclusion set
         | 
| 2274 | 
            -
                    # Start with hardcoded exclusions (these are ALWAYS excluded)
         | 
| 2275 | 
            -
                    hardcoded_exclusions = {"__init__", "MEMORIES", "TODOWRITE", "INSTRUCTIONS", 
         | 
| 2276 | 
            -
                                           "README", "pm", "PM", "project_manager"}
         | 
| 2277 | 
            -
                    
         | 
| 2278 | 
            -
                    # Get case sensitivity setting
         | 
| 2279 | 
            -
                    case_sensitive = config.get('agent_deployment.case_sensitive', False)
         | 
| 2280 | 
            -
                    
         | 
| 2281 | 
            -
                    # Filter out excluded agents
         | 
| 2282 | 
            -
                    filtered_files = []
         | 
| 2283 | 
            -
                    excluded_count = 0
         | 
| 2284 | 
            -
                    
         | 
| 2285 | 
            -
                    for f in template_files:
         | 
| 2286 | 
            -
                        agent_name = f.stem
         | 
| 2287 | 
            -
                        
         | 
| 2288 | 
            -
                        # Check hardcoded exclusions (always case-sensitive)
         | 
| 2289 | 
            -
                        if agent_name in hardcoded_exclusions:
         | 
| 2290 | 
            -
                            self.logger.debug(f"Excluding {agent_name}: hardcoded system exclusion")
         | 
| 2291 | 
            -
                            excluded_count += 1
         | 
| 2292 | 
            -
                            continue
         | 
| 2293 | 
            -
                        
         | 
| 2294 | 
            -
                        # Check file patterns
         | 
| 2295 | 
            -
                        if agent_name.startswith(".") or agent_name.endswith(".backup"):
         | 
| 2296 | 
            -
                            self.logger.debug(f"Excluding {agent_name}: file pattern exclusion")
         | 
| 2297 | 
            -
                            excluded_count += 1
         | 
| 2298 | 
            -
                            continue
         | 
| 2299 | 
            -
                        
         | 
| 2300 | 
            -
                        # Check user-configured exclusions
         | 
| 2301 | 
            -
                        compare_name = agent_name.lower() if not case_sensitive else agent_name
         | 
| 2302 | 
            -
                        if compare_name in excluded_agents:
         | 
| 2303 | 
            -
                            self.logger.info(f"Excluding {agent_name}: user-configured exclusion")
         | 
| 2304 | 
            -
                            excluded_count += 1
         | 
| 2305 | 
            -
                            continue
         | 
| 2306 | 
            -
                        
         | 
| 2307 | 
            -
                        # Agent is not excluded, add to filtered list
         | 
| 2308 | 
            -
                        filtered_files.append(f)
         | 
| 2309 | 
            -
                    
         | 
| 2310 | 
            -
                    if excluded_count > 0:
         | 
| 2311 | 
            -
                        self.logger.info(f"Excluded {excluded_count} agents from deployment")
         | 
| 2312 | 
            -
                    
         | 
| 2313 | 
            -
                    return filtered_files
         | 
| 2314 | 
            -
                
         | 
| 2315 | 
            -
                def _deploy_single_agent(self, template_file: Path, agents_dir: Path, 
         | 
| 2316 | 
            -
                                        base_agent_data: dict, base_agent_version: tuple,
         | 
| 2317 | 
            -
                                        force_rebuild: bool, deployment_mode: str, 
         | 
| 2318 | 
            -
                                        results: Dict[str, Any]) -> None:
         | 
| 709 | 
            +
                    """Get and filter template files based on exclusion rules."""
         | 
| 710 | 
            +
                    return self.discovery_service.get_filtered_templates(excluded_agents, config)
         | 
| 711 | 
            +
             | 
| 712 | 
            +
                def _deploy_single_agent(
         | 
| 713 | 
            +
                    self,
         | 
| 714 | 
            +
                    template_file: Path,
         | 
| 715 | 
            +
                    agents_dir: Path,
         | 
| 716 | 
            +
                    base_agent_data: dict,
         | 
| 717 | 
            +
                    base_agent_version: tuple,
         | 
| 718 | 
            +
                    force_rebuild: bool,
         | 
| 719 | 
            +
                    deployment_mode: str,
         | 
| 720 | 
            +
                    results: Dict[str, Any],
         | 
| 721 | 
            +
                ) -> None:
         | 
| 2319 722 | 
             
                    """
         | 
| 2320 723 | 
             
                    Deploy a single agent template.
         | 
| 2321 | 
            -
             | 
| 724 | 
            +
             | 
| 2322 725 | 
             
                    WHY: Extracting single agent deployment logic reduces complexity
         | 
| 2323 726 | 
             
                    and makes the main deployment loop more readable.
         | 
| 2324 | 
            -
             | 
| 727 | 
            +
             | 
| 2325 728 | 
             
                    Args:
         | 
| 2326 729 | 
             
                        template_file: Agent template file
         | 
| 2327 730 | 
             
                        agents_dir: Target agents directory
         | 
| @@ -2334,36 +737,50 @@ metadata: | |
| 2334 737 | 
             
                    try:
         | 
| 2335 738 | 
             
                        # METRICS: Track individual agent deployment time
         | 
| 2336 739 | 
             
                        agent_start_time = time.time()
         | 
| 2337 | 
            -
             | 
| 740 | 
            +
             | 
| 2338 741 | 
             
                        agent_name = template_file.stem
         | 
| 2339 742 | 
             
                        target_file = agents_dir / f"{agent_name}.md"
         | 
| 2340 | 
            -
             | 
| 743 | 
            +
             | 
| 2341 744 | 
             
                        # Check if agent needs update
         | 
| 2342 745 | 
             
                        needs_update, is_migration, reason = self._check_update_status(
         | 
| 2343 | 
            -
                            target_file, | 
| 2344 | 
            -
                             | 
| 746 | 
            +
                            target_file,
         | 
| 747 | 
            +
                            template_file,
         | 
| 748 | 
            +
                            base_agent_version,
         | 
| 749 | 
            +
                            force_rebuild,
         | 
| 750 | 
            +
                            deployment_mode,
         | 
| 2345 751 | 
             
                        )
         | 
| 2346 | 
            -
             | 
| 752 | 
            +
             | 
| 2347 753 | 
             
                        # Skip if exists and doesn't need update (only in update mode)
         | 
| 2348 | 
            -
                        if  | 
| 754 | 
            +
                        if (
         | 
| 755 | 
            +
                            target_file.exists()
         | 
| 756 | 
            +
                            and not needs_update
         | 
| 757 | 
            +
                            and deployment_mode != "project"
         | 
| 758 | 
            +
                        ):
         | 
| 2349 759 | 
             
                            results["skipped"].append(agent_name)
         | 
| 2350 760 | 
             
                            self.logger.debug(f"Skipped up-to-date agent: {agent_name}")
         | 
| 2351 761 | 
             
                            return
         | 
| 2352 | 
            -
             | 
| 762 | 
            +
             | 
| 2353 763 | 
             
                        # Build the agent file as markdown with YAML frontmatter
         | 
| 2354 | 
            -
                        agent_content = self. | 
| 2355 | 
            -
             | 
| 764 | 
            +
                        agent_content = self.template_builder.build_agent_markdown(
         | 
| 765 | 
            +
                            agent_name, template_file, base_agent_data
         | 
| 766 | 
            +
                        )
         | 
| 767 | 
            +
             | 
| 2356 768 | 
             
                        # Write the agent file
         | 
| 2357 769 | 
             
                        is_update = target_file.exists()
         | 
| 2358 770 | 
             
                        target_file.write_text(agent_content)
         | 
| 2359 | 
            -
             | 
| 771 | 
            +
             | 
| 2360 772 | 
             
                        # Record metrics and update results
         | 
| 2361 773 | 
             
                        self._record_agent_deployment(
         | 
| 2362 | 
            -
                            agent_name, | 
| 2363 | 
            -
                             | 
| 2364 | 
            -
                             | 
| 774 | 
            +
                            agent_name,
         | 
| 775 | 
            +
                            template_file,
         | 
| 776 | 
            +
                            target_file,
         | 
| 777 | 
            +
                            is_update,
         | 
| 778 | 
            +
                            is_migration,
         | 
| 779 | 
            +
                            reason,
         | 
| 780 | 
            +
                            agent_start_time,
         | 
| 781 | 
            +
                            results,
         | 
| 2365 782 | 
             
                        )
         | 
| 2366 | 
            -
             | 
| 783 | 
            +
             | 
| 2367 784 | 
             
                    except AgentDeploymentError as e:
         | 
| 2368 785 | 
             
                        # Re-raise our custom exceptions
         | 
| 2369 786 | 
             
                        self.logger.error(str(e))
         | 
| @@ -2373,40 +790,47 @@ metadata: | |
| 2373 790 | 
             
                        error_msg = f"Failed to build {template_file.name}: {e}"
         | 
| 2374 791 | 
             
                        self.logger.error(error_msg)
         | 
| 2375 792 | 
             
                        results["errors"].append(error_msg)
         | 
| 2376 | 
            -
             | 
| 2377 | 
            -
                def _check_update_status( | 
| 2378 | 
            -
             | 
| 2379 | 
            -
             | 
| 793 | 
            +
             | 
| 794 | 
            +
                def _check_update_status(
         | 
| 795 | 
            +
                    self,
         | 
| 796 | 
            +
                    target_file: Path,
         | 
| 797 | 
            +
                    template_file: Path,
         | 
| 798 | 
            +
                    base_agent_version: tuple,
         | 
| 799 | 
            +
                    force_rebuild: bool,
         | 
| 800 | 
            +
                    deployment_mode: str,
         | 
| 801 | 
            +
                ) -> tuple:
         | 
| 2380 802 | 
             
                    """
         | 
| 2381 803 | 
             
                    Check if agent needs update and determine status.
         | 
| 2382 | 
            -
             | 
| 804 | 
            +
             | 
| 2383 805 | 
             
                    WHY: Centralized update checking logic ensures consistent
         | 
| 2384 806 | 
             
                    version comparison and migration detection.
         | 
| 2385 | 
            -
             | 
| 807 | 
            +
             | 
| 2386 808 | 
             
                    Args:
         | 
| 2387 809 | 
             
                        target_file: Target agent file
         | 
| 2388 810 | 
             
                        template_file: Template file
         | 
| 2389 811 | 
             
                        base_agent_version: Base agent version
         | 
| 2390 812 | 
             
                        force_rebuild: Whether to force rebuild
         | 
| 2391 813 | 
             
                        deployment_mode: Deployment mode
         | 
| 2392 | 
            -
             | 
| 814 | 
            +
             | 
| 2393 815 | 
             
                    Returns:
         | 
| 2394 816 | 
             
                        Tuple of (needs_update, is_migration, reason)
         | 
| 2395 817 | 
             
                    """
         | 
| 2396 818 | 
             
                    needs_update = force_rebuild
         | 
| 2397 819 | 
             
                    is_migration = False
         | 
| 2398 820 | 
             
                    reason = ""
         | 
| 2399 | 
            -
             | 
| 821 | 
            +
             | 
| 2400 822 | 
             
                    # In project deployment mode, always deploy regardless of version
         | 
| 2401 823 | 
             
                    if deployment_mode == "project":
         | 
| 2402 824 | 
             
                        if target_file.exists():
         | 
| 2403 825 | 
             
                            needs_update = True
         | 
| 2404 | 
            -
                            self.logger.debug( | 
| 826 | 
            +
                            self.logger.debug(
         | 
| 827 | 
            +
                                f"Project deployment mode: will deploy {template_file.stem}"
         | 
| 828 | 
            +
                            )
         | 
| 2405 829 | 
             
                        else:
         | 
| 2406 830 | 
             
                            needs_update = True
         | 
| 2407 831 | 
             
                    elif not needs_update and target_file.exists():
         | 
| 2408 832 | 
             
                        # In update mode, check version compatibility
         | 
| 2409 | 
            -
                        needs_update, reason = self. | 
| 833 | 
            +
                        needs_update, reason = self.version_manager.check_agent_needs_update(
         | 
| 2410 834 | 
             
                            target_file, template_file, base_agent_version
         | 
| 2411 835 | 
             
                        )
         | 
| 2412 836 | 
             
                        if needs_update:
         | 
| @@ -2415,20 +839,29 @@ metadata: | |
| 2415 839 | 
             
                                is_migration = True
         | 
| 2416 840 | 
             
                                self.logger.info(f"Migrating agent {template_file.stem}: {reason}")
         | 
| 2417 841 | 
             
                            else:
         | 
| 2418 | 
            -
                                self.logger.info( | 
| 2419 | 
            -
             | 
| 842 | 
            +
                                self.logger.info(
         | 
| 843 | 
            +
                                    f"Agent {template_file.stem} needs update: {reason}"
         | 
| 844 | 
            +
                                )
         | 
| 845 | 
            +
             | 
| 2420 846 | 
             
                    return needs_update, is_migration, reason
         | 
| 2421 | 
            -
             | 
| 2422 | 
            -
                def _record_agent_deployment( | 
| 2423 | 
            -
             | 
| 2424 | 
            -
             | 
| 2425 | 
            -
             | 
| 847 | 
            +
             | 
| 848 | 
            +
                def _record_agent_deployment(
         | 
| 849 | 
            +
                    self,
         | 
| 850 | 
            +
                    agent_name: str,
         | 
| 851 | 
            +
                    template_file: Path,
         | 
| 852 | 
            +
                    target_file: Path,
         | 
| 853 | 
            +
                    is_update: bool,
         | 
| 854 | 
            +
                    is_migration: bool,
         | 
| 855 | 
            +
                    reason: str,
         | 
| 856 | 
            +
                    agent_start_time: float,
         | 
| 857 | 
            +
                    results: Dict[str, Any],
         | 
| 858 | 
            +
                ) -> None:
         | 
| 2426 859 | 
             
                    """
         | 
| 2427 860 | 
             
                    Record deployment metrics and update results.
         | 
| 2428 | 
            -
             | 
| 861 | 
            +
             | 
| 2429 862 | 
             
                    WHY: Centralized metrics recording ensures consistent tracking
         | 
| 2430 863 | 
             
                    of deployment performance and statistics.
         | 
| 2431 | 
            -
             | 
| 864 | 
            +
             | 
| 2432 865 | 
             
                    Args:
         | 
| 2433 866 | 
             
                        agent_name: Name of the agent
         | 
| 2434 867 | 
             
                        template_file: Template file
         | 
| @@ -2442,226 +875,95 @@ metadata: | |
| 2442 875 | 
             
                    # METRICS: Record deployment time for this agent
         | 
| 2443 876 | 
             
                    agent_deployment_time = (time.time() - agent_start_time) * 1000  # Convert to ms
         | 
| 2444 877 | 
             
                    results["metrics"]["agent_timings"][agent_name] = agent_deployment_time
         | 
| 2445 | 
            -
             | 
| 878 | 
            +
             | 
| 2446 879 | 
             
                    # METRICS: Update agent type deployment counts
         | 
| 2447 | 
            -
                    self._deployment_metrics[ | 
| 2448 | 
            -
                        self._deployment_metrics[ | 
| 2449 | 
            -
                    
         | 
| 880 | 
            +
                    self._deployment_metrics["agent_type_counts"][agent_name] = (
         | 
| 881 | 
            +
                        self._deployment_metrics["agent_type_counts"].get(agent_name, 0) + 1
         | 
| 882 | 
            +
                    )
         | 
| 883 | 
            +
             | 
| 2450 884 | 
             
                    deployment_info = {
         | 
| 2451 885 | 
             
                        "name": agent_name,
         | 
| 2452 886 | 
             
                        "template": str(template_file),
         | 
| 2453 887 | 
             
                        "target": str(target_file),
         | 
| 2454 | 
            -
                        "deployment_time_ms": agent_deployment_time
         | 
| 888 | 
            +
                        "deployment_time_ms": agent_deployment_time,
         | 
| 2455 889 | 
             
                    }
         | 
| 2456 | 
            -
             | 
| 890 | 
            +
             | 
| 2457 891 | 
             
                    if is_migration:
         | 
| 2458 892 | 
             
                        deployment_info["reason"] = reason
         | 
| 2459 893 | 
             
                        results["migrated"].append(deployment_info)
         | 
| 2460 | 
            -
                        self.logger.info( | 
| 2461 | 
            -
             | 
| 894 | 
            +
                        self.logger.info(
         | 
| 895 | 
            +
                            f"Successfully migrated agent: {agent_name} to semantic versioning"
         | 
| 896 | 
            +
                        )
         | 
| 897 | 
            +
             | 
| 2462 898 | 
             
                        # METRICS: Track migration statistics
         | 
| 2463 | 
            -
                        self._deployment_metrics[ | 
| 2464 | 
            -
                        self._deployment_metrics[ | 
| 2465 | 
            -
             | 
| 899 | 
            +
                        self._deployment_metrics["migrations_performed"] += 1
         | 
| 900 | 
            +
                        self._deployment_metrics["version_migration_count"] += 1
         | 
| 901 | 
            +
             | 
| 2466 902 | 
             
                    elif is_update:
         | 
| 2467 903 | 
             
                        results["updated"].append(deployment_info)
         | 
| 2468 904 | 
             
                        self.logger.debug(f"Updated agent: {agent_name}")
         | 
| 2469 905 | 
             
                    else:
         | 
| 2470 906 | 
             
                        results["deployed"].append(deployment_info)
         | 
| 2471 907 | 
             
                        self.logger.debug(f"Built and deployed agent: {agent_name}")
         | 
| 2472 | 
            -
             | 
| 908 | 
            +
             | 
| 2473 909 | 
             
                def _validate_and_repair_existing_agents(self, agents_dir: Path) -> Dict[str, Any]:
         | 
| 2474 | 
            -
                    """
         | 
| 2475 | 
            -
                     | 
| 2476 | 
            -
             | 
| 2477 | 
            -
                     | 
| 2478 | 
            -
                     | 
| 2479 | 
            -
             | 
| 2480 | 
            -
                    
         | 
| 2481 | 
            -
                    WHY: Ensures all existing agents have valid YAML frontmatter before deployment,
         | 
| 2482 | 
            -
                    preventing runtime errors in Claude Code when loading agents.
         | 
| 2483 | 
            -
                    
         | 
| 2484 | 
            -
                    Args:
         | 
| 2485 | 
            -
                        agents_dir: Directory containing agent .md files
         | 
| 2486 | 
            -
                        
         | 
| 2487 | 
            -
                    Returns:
         | 
| 2488 | 
            -
                        Dictionary with validation results:
         | 
| 2489 | 
            -
                        - repaired: List of agent names that were repaired
         | 
| 2490 | 
            -
                        - replaced: List of agent names marked for replacement
         | 
| 2491 | 
            -
                        - errors: List of validation errors
         | 
| 2492 | 
            -
                    """
         | 
| 2493 | 
            -
                    results = {
         | 
| 2494 | 
            -
                        "repaired": [],
         | 
| 2495 | 
            -
                        "replaced": [],
         | 
| 2496 | 
            -
                        "errors": []
         | 
| 2497 | 
            -
                    }
         | 
| 2498 | 
            -
                    
         | 
| 2499 | 
            -
                    try:
         | 
| 2500 | 
            -
                        # Import frontmatter validator
         | 
| 2501 | 
            -
                        from claude_mpm.agents.frontmatter_validator import FrontmatterValidator
         | 
| 2502 | 
            -
                        validator = FrontmatterValidator()
         | 
| 2503 | 
            -
                        
         | 
| 2504 | 
            -
                        # Find existing agent files
         | 
| 2505 | 
            -
                        agent_files = list(agents_dir.glob("*.md"))
         | 
| 2506 | 
            -
                        
         | 
| 2507 | 
            -
                        if not agent_files:
         | 
| 2508 | 
            -
                            self.logger.debug("No existing agent files to validate")
         | 
| 2509 | 
            -
                            return results
         | 
| 2510 | 
            -
                        
         | 
| 2511 | 
            -
                        self.logger.debug(f"Validating frontmatter in {len(agent_files)} existing agents")
         | 
| 2512 | 
            -
                        
         | 
| 2513 | 
            -
                        for agent_file in agent_files:
         | 
| 2514 | 
            -
                            try:
         | 
| 2515 | 
            -
                                agent_name = agent_file.stem
         | 
| 2516 | 
            -
                                
         | 
| 2517 | 
            -
                                # Read agent file content
         | 
| 2518 | 
            -
                                content = agent_file.read_text()
         | 
| 2519 | 
            -
                                
         | 
| 2520 | 
            -
                                # Check if this is a system agent (authored by claude-mpm)
         | 
| 2521 | 
            -
                                # Only repair system agents, leave user agents alone
         | 
| 2522 | 
            -
                                if "author: claude-mpm" not in content and "author: 'claude-mpm'" not in content:
         | 
| 2523 | 
            -
                                    self.logger.debug(f"Skipping validation for user agent: {agent_name}")
         | 
| 2524 | 
            -
                                    continue
         | 
| 2525 | 
            -
                                
         | 
| 2526 | 
            -
                                # Extract and validate frontmatter
         | 
| 2527 | 
            -
                                if not content.startswith("---"):
         | 
| 2528 | 
            -
                                    # No frontmatter at all - mark for replacement
         | 
| 2529 | 
            -
                                    self.logger.warning(f"Agent {agent_name} has no frontmatter, marking for replacement")
         | 
| 2530 | 
            -
                                    results["replaced"].append(agent_name)
         | 
| 2531 | 
            -
                                    # Delete the file so it will be recreated
         | 
| 2532 | 
            -
                                    agent_file.unlink()
         | 
| 2533 | 
            -
                                    continue
         | 
| 2534 | 
            -
                                
         | 
| 2535 | 
            -
                                # Try to extract frontmatter
         | 
| 2536 | 
            -
                                try:
         | 
| 2537 | 
            -
                                    end_marker = content.find("\n---\n", 4)
         | 
| 2538 | 
            -
                                    if end_marker == -1:
         | 
| 2539 | 
            -
                                        end_marker = content.find("\n---\r\n", 4)
         | 
| 2540 | 
            -
                                    
         | 
| 2541 | 
            -
                                    if end_marker == -1:
         | 
| 2542 | 
            -
                                        # Broken frontmatter - mark for replacement
         | 
| 2543 | 
            -
                                        self.logger.warning(f"Agent {agent_name} has broken frontmatter, marking for replacement")
         | 
| 2544 | 
            -
                                        results["replaced"].append(agent_name)
         | 
| 2545 | 
            -
                                        # Delete the file so it will be recreated
         | 
| 2546 | 
            -
                                        agent_file.unlink()
         | 
| 2547 | 
            -
                                        continue
         | 
| 2548 | 
            -
                                    
         | 
| 2549 | 
            -
                                    # Validate frontmatter with the validator
         | 
| 2550 | 
            -
                                    validation_result = validator.validate_file(agent_file)
         | 
| 2551 | 
            -
                                    
         | 
| 2552 | 
            -
                                    if not validation_result.is_valid:
         | 
| 2553 | 
            -
                                        # Check if it can be corrected
         | 
| 2554 | 
            -
                                        if validation_result.corrected_frontmatter:
         | 
| 2555 | 
            -
                                            # Apply corrections
         | 
| 2556 | 
            -
                                            correction_result = validator.correct_file(agent_file, dry_run=False)
         | 
| 2557 | 
            -
                                            if correction_result.corrections:
         | 
| 2558 | 
            -
                                                results["repaired"].append(agent_name)
         | 
| 2559 | 
            -
                                                self.logger.info(f"Repaired frontmatter for agent {agent_name}")
         | 
| 2560 | 
            -
                                                for correction in correction_result.corrections:
         | 
| 2561 | 
            -
                                                    self.logger.debug(f"  - {correction}")
         | 
| 2562 | 
            -
                                        else:
         | 
| 2563 | 
            -
                                            # Cannot be corrected - mark for replacement
         | 
| 2564 | 
            -
                                            self.logger.warning(f"Agent {agent_name} has invalid frontmatter that cannot be repaired, marking for replacement")
         | 
| 2565 | 
            -
                                            results["replaced"].append(agent_name)
         | 
| 2566 | 
            -
                                            # Delete the file so it will be recreated
         | 
| 2567 | 
            -
                                            agent_file.unlink()
         | 
| 2568 | 
            -
                                    elif validation_result.warnings:
         | 
| 2569 | 
            -
                                        # Has warnings but is valid
         | 
| 2570 | 
            -
                                        for warning in validation_result.warnings:
         | 
| 2571 | 
            -
                                            self.logger.debug(f"Agent {agent_name} warning: {warning}")
         | 
| 2572 | 
            -
                                    
         | 
| 2573 | 
            -
                                except Exception as e:
         | 
| 2574 | 
            -
                                    # Any error in parsing - mark for replacement
         | 
| 2575 | 
            -
                                    self.logger.warning(f"Failed to parse frontmatter for {agent_name}: {e}, marking for replacement")
         | 
| 2576 | 
            -
                                    results["replaced"].append(agent_name)
         | 
| 2577 | 
            -
                                    # Delete the file so it will be recreated
         | 
| 2578 | 
            -
                                    try:
         | 
| 2579 | 
            -
                                        agent_file.unlink()
         | 
| 2580 | 
            -
                                    except Exception:
         | 
| 2581 | 
            -
                                        pass
         | 
| 2582 | 
            -
                                
         | 
| 2583 | 
            -
                            except Exception as e:
         | 
| 2584 | 
            -
                                error_msg = f"Failed to validate agent {agent_file.name}: {e}"
         | 
| 2585 | 
            -
                                self.logger.error(error_msg)
         | 
| 2586 | 
            -
                                results["errors"].append(error_msg)
         | 
| 2587 | 
            -
                        
         | 
| 2588 | 
            -
                    except ImportError:
         | 
| 2589 | 
            -
                        self.logger.warning("FrontmatterValidator not available, skipping validation")
         | 
| 2590 | 
            -
                    except Exception as e:
         | 
| 2591 | 
            -
                        error_msg = f"Agent validation failed: {e}"
         | 
| 2592 | 
            -
                        self.logger.error(error_msg)
         | 
| 2593 | 
            -
                        results["errors"].append(error_msg)
         | 
| 2594 | 
            -
                    
         | 
| 2595 | 
            -
                    return results
         | 
| 2596 | 
            -
                
         | 
| 910 | 
            +
                    """Validate and repair broken frontmatter in existing agent files."""
         | 
| 911 | 
            +
                    from .agent_frontmatter_validator import AgentFrontmatterValidator
         | 
| 912 | 
            +
             | 
| 913 | 
            +
                    validator = AgentFrontmatterValidator(self.logger)
         | 
| 914 | 
            +
                    return validator.validate_and_repair_existing_agents(agents_dir)
         | 
| 915 | 
            +
             | 
| 2597 916 | 
             
                # ================================================================================
         | 
| 2598 917 | 
             
                # Interface Adapter Methods
         | 
| 2599 918 | 
             
                # ================================================================================
         | 
| 2600 919 | 
             
                # These methods adapt the existing implementation to comply with AgentDeploymentInterface
         | 
| 2601 | 
            -
             | 
| 920 | 
            +
             | 
| 2602 921 | 
             
                def validate_agent(self, agent_path: Path) -> tuple[bool, List[str]]:
         | 
| 2603 922 | 
             
                    """Validate agent configuration and structure.
         | 
| 2604 | 
            -
             | 
| 923 | 
            +
             | 
| 2605 924 | 
             
                    WHY: This adapter method provides interface compliance while leveraging
         | 
| 2606 925 | 
             
                    the existing validation logic in _check_agent_needs_update and other methods.
         | 
| 2607 | 
            -
             | 
| 926 | 
            +
             | 
| 2608 927 | 
             
                    Args:
         | 
| 2609 928 | 
             
                        agent_path: Path to agent configuration file
         | 
| 2610 | 
            -
             | 
| 929 | 
            +
             | 
| 2611 930 | 
             
                    Returns:
         | 
| 2612 931 | 
             
                        Tuple of (is_valid, list_of_errors)
         | 
| 2613 932 | 
             
                    """
         | 
| 2614 933 | 
             
                    errors = []
         | 
| 2615 | 
            -
             | 
| 934 | 
            +
             | 
| 2616 935 | 
             
                    try:
         | 
| 2617 936 | 
             
                        if not agent_path.exists():
         | 
| 2618 937 | 
             
                            return False, [f"Agent file not found: {agent_path}"]
         | 
| 2619 | 
            -
             | 
| 938 | 
            +
             | 
| 2620 939 | 
             
                        content = agent_path.read_text()
         | 
| 2621 | 
            -
             | 
| 940 | 
            +
             | 
| 2622 941 | 
             
                        # Check YAML frontmatter format
         | 
| 2623 942 | 
             
                        if not content.startswith("---"):
         | 
| 2624 943 | 
             
                            errors.append("Missing YAML frontmatter")
         | 
| 2625 | 
            -
             | 
| 944 | 
            +
             | 
| 2626 945 | 
             
                        # Extract and validate version
         | 
| 2627 946 | 
             
                        import re
         | 
| 2628 | 
            -
             | 
| 947 | 
            +
             | 
| 948 | 
            +
                        version_match = re.search(
         | 
| 949 | 
            +
                            r'^version:\s*["\']?(.+?)["\']?$', content, re.MULTILINE
         | 
| 950 | 
            +
                        )
         | 
| 2629 951 | 
             
                        if not version_match:
         | 
| 2630 952 | 
             
                            errors.append("Missing version field in frontmatter")
         | 
| 2631 | 
            -
             | 
| 953 | 
            +
             | 
| 2632 954 | 
             
                        # Check for required fields
         | 
| 2633 | 
            -
                        required_fields = [ | 
| 955 | 
            +
                        required_fields = ["name", "description", "tools"]
         | 
| 2634 956 | 
             
                        for field in required_fields:
         | 
| 2635 | 
            -
                            field_match = re.search(rf | 
| 957 | 
            +
                            field_match = re.search(rf"^{field}:\s*.+$", content, re.MULTILINE)
         | 
| 2636 958 | 
             
                            if not field_match:
         | 
| 2637 959 | 
             
                                errors.append(f"Missing required field: {field}")
         | 
| 2638 | 
            -
             | 
| 960 | 
            +
             | 
| 2639 961 | 
             
                        # If no errors, validation passed
         | 
| 2640 962 | 
             
                        return len(errors) == 0, errors
         | 
| 2641 | 
            -
             | 
| 963 | 
            +
             | 
| 2642 964 | 
             
                    except Exception as e:
         | 
| 2643 965 | 
             
                        return False, [f"Validation error: {str(e)}"]
         | 
| 2644 | 
            -
             | 
| 966 | 
            +
             | 
| 2645 967 | 
             
                def get_deployment_status(self) -> Dict[str, Any]:
         | 
| 2646 | 
            -
                    """Get current deployment status and metrics.
         | 
| 2647 | 
            -
                    
         | 
| 2648 | 
            -
                    WHY: This adapter method provides interface compliance by wrapping
         | 
| 2649 | 
            -
                    verify_deployment and adding deployment metrics.
         | 
| 2650 | 
            -
                    
         | 
| 2651 | 
            -
                    Returns:
         | 
| 2652 | 
            -
                        Dictionary with deployment status information
         | 
| 2653 | 
            -
                    """
         | 
| 2654 | 
            -
                    # Get verification results
         | 
| 2655 | 
            -
                    verification = self.verify_deployment()
         | 
| 2656 | 
            -
                    
         | 
| 2657 | 
            -
                    # Add deployment metrics
         | 
| 2658 | 
            -
                    status = {
         | 
| 2659 | 
            -
                        "deployment_metrics": self._deployment_metrics.copy(),
         | 
| 2660 | 
            -
                        "verification": verification,
         | 
| 2661 | 
            -
                        "agents_deployed": len(verification.get("agents_found", [])),
         | 
| 2662 | 
            -
                        "agents_needing_migration": len(verification.get("agents_needing_migration", [])),
         | 
| 2663 | 
            -
                        "has_warnings": len(verification.get("warnings", [])) > 0,
         | 
| 2664 | 
            -
                        "environment_configured": bool(verification.get("environment", {}))
         | 
| 2665 | 
            -
                    }
         | 
| 2666 | 
            -
                    
         | 
| 2667 | 
            -
                    return status
         | 
| 968 | 
            +
                    """Get current deployment status and metrics."""
         | 
| 969 | 
            +
                    return self.metrics_collector.get_deployment_status()
         |