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