claude-mpm 4.1.1__py3-none-any.whl → 4.1.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/BUILD_NUMBER +1 -1
- claude_mpm/VERSION +1 -1
- claude_mpm/__main__.py +1 -1
- claude_mpm/agents/BASE_PM.md +74 -46
- claude_mpm/agents/INSTRUCTIONS.md +11 -153
- claude_mpm/agents/WORKFLOW.md +61 -321
- claude_mpm/agents/__init__.py +11 -11
- claude_mpm/agents/agent_loader.py +23 -20
- claude_mpm/agents/agent_loader_integration.py +1 -1
- claude_mpm/agents/agents_metadata.py +27 -0
- claude_mpm/agents/async_agent_loader.py +5 -8
- claude_mpm/agents/base_agent_loader.py +36 -25
- claude_mpm/agents/frontmatter_validator.py +6 -6
- claude_mpm/agents/schema/agent_schema.json +1 -1
- claude_mpm/agents/system_agent_config.py +9 -9
- claude_mpm/agents/templates/api_qa.json +47 -2
- claude_mpm/agents/templates/engineer.json +33 -11
- claude_mpm/agents/templates/imagemagick.json +256 -0
- claude_mpm/agents/templates/qa.json +41 -2
- claude_mpm/agents/templates/ticketing.json +5 -5
- claude_mpm/agents/templates/web_qa.json +50 -2
- claude_mpm/cli/__init__.py +51 -46
- claude_mpm/cli/__main__.py +1 -1
- claude_mpm/cli/commands/__init__.py +10 -12
- claude_mpm/cli/commands/agent_manager.py +186 -181
- claude_mpm/cli/commands/agents.py +648 -1098
- claude_mpm/cli/commands/aggregate.py +30 -29
- claude_mpm/cli/commands/cleanup.py +50 -44
- claude_mpm/cli/commands/cleanup_orphaned_agents.py +25 -25
- claude_mpm/cli/commands/config.py +162 -127
- claude_mpm/cli/commands/doctor.py +52 -62
- claude_mpm/cli/commands/info.py +37 -25
- claude_mpm/cli/commands/mcp.py +3 -7
- claude_mpm/cli/commands/mcp_command_router.py +14 -18
- claude_mpm/cli/commands/mcp_install_commands.py +28 -23
- claude_mpm/cli/commands/mcp_pipx_config.py +58 -49
- claude_mpm/cli/commands/mcp_server_commands.py +23 -17
- claude_mpm/cli/commands/memory.py +339 -967
- claude_mpm/cli/commands/monitor.py +117 -88
- claude_mpm/cli/commands/run.py +233 -542
- claude_mpm/cli/commands/socketio_monitor.py +17 -19
- claude_mpm/cli/commands/tickets.py +92 -92
- claude_mpm/cli/parser.py +1 -5
- claude_mpm/cli/parsers/__init__.py +1 -1
- claude_mpm/cli/parsers/agent_manager_parser.py +50 -98
- claude_mpm/cli/parsers/agents_parser.py +2 -3
- claude_mpm/cli/parsers/base_parser.py +7 -5
- claude_mpm/cli/parsers/mcp_parser.py +4 -2
- claude_mpm/cli/parsers/monitor_parser.py +26 -18
- claude_mpm/cli/shared/__init__.py +10 -10
- claude_mpm/cli/shared/argument_patterns.py +57 -71
- claude_mpm/cli/shared/base_command.py +61 -53
- claude_mpm/cli/shared/error_handling.py +62 -58
- claude_mpm/cli/shared/output_formatters.py +78 -77
- claude_mpm/cli/startup_logging.py +280 -172
- claude_mpm/cli/utils.py +10 -11
- claude_mpm/cli_module/__init__.py +1 -1
- claude_mpm/cli_module/args.py +1 -1
- claude_mpm/cli_module/migration_example.py +5 -5
- claude_mpm/config/__init__.py +9 -9
- claude_mpm/config/agent_config.py +15 -14
- claude_mpm/config/experimental_features.py +4 -4
- claude_mpm/config/paths.py +0 -1
- claude_mpm/config/socketio_config.py +5 -6
- claude_mpm/constants.py +1 -2
- claude_mpm/core/__init__.py +8 -8
- claude_mpm/core/agent_name_normalizer.py +1 -1
- claude_mpm/core/agent_registry.py +22 -29
- claude_mpm/core/agent_session_manager.py +3 -3
- claude_mpm/core/base_service.py +7 -15
- claude_mpm/core/cache.py +4 -6
- claude_mpm/core/claude_runner.py +85 -113
- claude_mpm/core/config.py +43 -28
- claude_mpm/core/config_aliases.py +0 -9
- claude_mpm/core/config_constants.py +52 -30
- claude_mpm/core/constants.py +0 -1
- claude_mpm/core/container.py +18 -27
- claude_mpm/core/exceptions.py +2 -2
- claude_mpm/core/factories.py +10 -12
- claude_mpm/core/framework_loader.py +500 -680
- claude_mpm/core/hook_manager.py +26 -22
- claude_mpm/core/hook_performance_config.py +58 -47
- claude_mpm/core/injectable_service.py +1 -1
- claude_mpm/core/interactive_session.py +61 -152
- claude_mpm/core/interfaces.py +1 -100
- claude_mpm/core/lazy.py +5 -5
- claude_mpm/core/log_manager.py +587 -0
- claude_mpm/core/logger.py +125 -8
- claude_mpm/core/logging_config.py +15 -17
- claude_mpm/core/minimal_framework_loader.py +5 -8
- claude_mpm/core/oneshot_session.py +15 -33
- claude_mpm/core/optimized_agent_loader.py +4 -6
- claude_mpm/core/optimized_startup.py +2 -1
- claude_mpm/core/output_style_manager.py +147 -106
- claude_mpm/core/pm_hook_interceptor.py +0 -1
- claude_mpm/core/service_registry.py +11 -8
- claude_mpm/core/session_manager.py +1 -2
- claude_mpm/core/shared/__init__.py +1 -1
- claude_mpm/core/shared/config_loader.py +101 -97
- claude_mpm/core/shared/path_resolver.py +72 -68
- claude_mpm/core/shared/singleton_manager.py +56 -50
- claude_mpm/core/socketio_pool.py +26 -6
- claude_mpm/core/tool_access_control.py +4 -5
- claude_mpm/core/typing_utils.py +50 -59
- claude_mpm/core/unified_agent_registry.py +14 -19
- claude_mpm/core/unified_config.py +4 -6
- claude_mpm/core/unified_paths.py +197 -109
- claude_mpm/dashboard/open_dashboard.py +2 -4
- claude_mpm/experimental/cli_enhancements.py +51 -36
- claude_mpm/generators/agent_profile_generator.py +2 -4
- claude_mpm/hooks/base_hook.py +1 -2
- claude_mpm/hooks/claude_hooks/connection_pool.py +72 -26
- claude_mpm/hooks/claude_hooks/event_handlers.py +99 -154
- claude_mpm/hooks/claude_hooks/hook_handler.py +110 -720
- claude_mpm/hooks/claude_hooks/hook_handler_eventbus.py +104 -77
- claude_mpm/hooks/claude_hooks/hook_handler_original.py +1040 -0
- claude_mpm/hooks/claude_hooks/hook_handler_refactored.py +347 -0
- claude_mpm/hooks/claude_hooks/memory_integration.py +2 -4
- claude_mpm/hooks/claude_hooks/response_tracking.py +15 -11
- claude_mpm/hooks/claude_hooks/services/__init__.py +13 -0
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +190 -0
- claude_mpm/hooks/claude_hooks/services/duplicate_detector.py +106 -0
- claude_mpm/hooks/claude_hooks/services/state_manager.py +282 -0
- claude_mpm/hooks/claude_hooks/services/subagent_processor.py +374 -0
- claude_mpm/hooks/claude_hooks/tool_analysis.py +12 -18
- claude_mpm/hooks/memory_integration_hook.py +5 -5
- claude_mpm/hooks/tool_call_interceptor.py +1 -1
- claude_mpm/hooks/validation_hooks.py +4 -4
- claude_mpm/init.py +4 -9
- claude_mpm/models/__init__.py +2 -2
- claude_mpm/models/agent_session.py +11 -14
- claude_mpm/scripts/mcp_server.py +20 -11
- claude_mpm/scripts/mcp_wrapper.py +5 -5
- claude_mpm/scripts/mpm_doctor.py +321 -0
- claude_mpm/scripts/socketio_daemon.py +28 -25
- claude_mpm/scripts/socketio_daemon_hardened.py +298 -258
- claude_mpm/scripts/socketio_server_manager.py +116 -95
- claude_mpm/services/__init__.py +49 -49
- claude_mpm/services/agent_capabilities_service.py +12 -18
- claude_mpm/services/agents/__init__.py +22 -22
- claude_mpm/services/agents/agent_builder.py +140 -119
- claude_mpm/services/agents/deployment/__init__.py +3 -3
- claude_mpm/services/agents/deployment/agent_config_provider.py +9 -9
- claude_mpm/services/agents/deployment/agent_configuration_manager.py +19 -20
- claude_mpm/services/agents/deployment/agent_definition_factory.py +1 -5
- claude_mpm/services/agents/deployment/agent_deployment.py +129 -511
- claude_mpm/services/agents/deployment/agent_discovery_service.py +4 -8
- claude_mpm/services/agents/deployment/agent_environment_manager.py +2 -7
- claude_mpm/services/agents/deployment/agent_filesystem_manager.py +6 -10
- claude_mpm/services/agents/deployment/agent_format_converter.py +11 -15
- claude_mpm/services/agents/deployment/agent_frontmatter_validator.py +2 -3
- claude_mpm/services/agents/deployment/agent_lifecycle_manager.py +5 -5
- claude_mpm/services/agents/deployment/agent_metrics_collector.py +13 -19
- claude_mpm/services/agents/deployment/agent_restore_handler.py +0 -1
- claude_mpm/services/agents/deployment/agent_template_builder.py +26 -35
- claude_mpm/services/agents/deployment/agent_validator.py +0 -1
- claude_mpm/services/agents/deployment/agent_version_manager.py +7 -9
- claude_mpm/services/agents/deployment/agent_versioning.py +3 -3
- claude_mpm/services/agents/deployment/agents_directory_resolver.py +6 -7
- claude_mpm/services/agents/deployment/async_agent_deployment.py +51 -38
- claude_mpm/services/agents/deployment/base_agent_locator.py +132 -0
- claude_mpm/services/agents/deployment/config/__init__.py +1 -1
- claude_mpm/services/agents/deployment/config/deployment_config.py +7 -8
- claude_mpm/services/agents/deployment/deployment_results_manager.py +185 -0
- claude_mpm/services/agents/deployment/deployment_type_detector.py +1 -1
- claude_mpm/services/agents/deployment/deployment_wrapper.py +18 -18
- claude_mpm/services/agents/deployment/facade/__init__.py +1 -1
- claude_mpm/services/agents/deployment/facade/deployment_executor.py +0 -3
- claude_mpm/services/agents/deployment/facade/deployment_facade.py +3 -4
- claude_mpm/services/agents/deployment/interface_adapter.py +5 -7
- claude_mpm/services/agents/deployment/multi_source_deployment_service.py +345 -276
- claude_mpm/services/agents/deployment/pipeline/__init__.py +2 -2
- claude_mpm/services/agents/deployment/pipeline/pipeline_builder.py +1 -1
- claude_mpm/services/agents/deployment/pipeline/pipeline_context.py +6 -4
- claude_mpm/services/agents/deployment/pipeline/pipeline_executor.py +3 -3
- claude_mpm/services/agents/deployment/pipeline/steps/__init__.py +2 -2
- claude_mpm/services/agents/deployment/pipeline/steps/agent_processing_step.py +14 -13
- claude_mpm/services/agents/deployment/pipeline/steps/base_step.py +0 -1
- claude_mpm/services/agents/deployment/pipeline/steps/configuration_step.py +1 -1
- claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +8 -9
- claude_mpm/services/agents/deployment/pipeline/steps/validation_step.py +1 -1
- claude_mpm/services/agents/deployment/processors/__init__.py +1 -1
- claude_mpm/services/agents/deployment/processors/agent_processor.py +20 -16
- claude_mpm/services/agents/deployment/refactored_agent_deployment_service.py +5 -12
- claude_mpm/services/agents/deployment/results/__init__.py +1 -1
- claude_mpm/services/agents/deployment/results/deployment_result_builder.py +1 -1
- claude_mpm/services/agents/deployment/single_agent_deployer.py +315 -0
- claude_mpm/services/agents/deployment/strategies/__init__.py +2 -2
- claude_mpm/services/agents/deployment/strategies/base_strategy.py +1 -7
- claude_mpm/services/agents/deployment/strategies/project_strategy.py +1 -4
- claude_mpm/services/agents/deployment/strategies/system_strategy.py +2 -3
- claude_mpm/services/agents/deployment/strategies/user_strategy.py +3 -7
- claude_mpm/services/agents/deployment/validation/__init__.py +1 -1
- claude_mpm/services/agents/deployment/validation/agent_validator.py +1 -1
- claude_mpm/services/agents/deployment/validation/template_validator.py +2 -2
- claude_mpm/services/agents/deployment/validation/validation_result.py +2 -6
- claude_mpm/services/agents/loading/__init__.py +1 -1
- claude_mpm/services/agents/loading/agent_profile_loader.py +6 -12
- claude_mpm/services/agents/loading/base_agent_manager.py +5 -5
- claude_mpm/services/agents/loading/framework_agent_loader.py +2 -4
- claude_mpm/services/agents/management/__init__.py +1 -1
- claude_mpm/services/agents/management/agent_capabilities_generator.py +1 -3
- claude_mpm/services/agents/management/agent_management_service.py +5 -9
- claude_mpm/services/agents/memory/__init__.py +4 -4
- claude_mpm/services/agents/memory/agent_memory_manager.py +157 -503
- claude_mpm/services/agents/memory/agent_persistence_service.py +0 -2
- claude_mpm/services/agents/memory/content_manager.py +44 -38
- claude_mpm/services/agents/memory/memory_categorization_service.py +165 -0
- claude_mpm/services/agents/memory/memory_file_service.py +103 -0
- claude_mpm/services/agents/memory/memory_format_service.py +201 -0
- claude_mpm/services/agents/memory/memory_limits_service.py +99 -0
- claude_mpm/services/agents/memory/template_generator.py +4 -6
- claude_mpm/services/agents/registry/__init__.py +11 -7
- claude_mpm/services/agents/registry/deployed_agent_discovery.py +30 -27
- claude_mpm/services/agents/registry/modification_tracker.py +3 -6
- claude_mpm/services/async_session_logger.py +1 -2
- claude_mpm/services/claude_session_logger.py +1 -2
- claude_mpm/services/cli/__init__.py +18 -0
- claude_mpm/services/cli/agent_cleanup_service.py +407 -0
- claude_mpm/services/cli/agent_dependency_service.py +395 -0
- claude_mpm/services/cli/agent_listing_service.py +463 -0
- claude_mpm/services/cli/agent_output_formatter.py +605 -0
- claude_mpm/services/cli/agent_validation_service.py +589 -0
- claude_mpm/services/cli/dashboard_launcher.py +424 -0
- claude_mpm/services/cli/memory_crud_service.py +617 -0
- claude_mpm/services/cli/memory_output_formatter.py +604 -0
- claude_mpm/services/cli/session_manager.py +513 -0
- claude_mpm/services/cli/socketio_manager.py +498 -0
- claude_mpm/services/cli/startup_checker.py +370 -0
- claude_mpm/services/command_deployment_service.py +173 -0
- claude_mpm/services/command_handler_service.py +20 -22
- claude_mpm/services/core/__init__.py +25 -25
- claude_mpm/services/core/base.py +0 -5
- claude_mpm/services/core/cache_manager.py +311 -0
- claude_mpm/services/core/interfaces/__init__.py +32 -32
- claude_mpm/services/core/interfaces/agent.py +0 -21
- claude_mpm/services/core/interfaces/communication.py +0 -27
- claude_mpm/services/core/interfaces/infrastructure.py +0 -56
- claude_mpm/services/core/interfaces/service.py +0 -29
- claude_mpm/services/core/memory_manager.py +637 -0
- claude_mpm/services/core/path_resolver.py +498 -0
- claude_mpm/services/core/service_container.py +520 -0
- claude_mpm/services/core/service_interfaces.py +436 -0
- claude_mpm/services/diagnostics/__init__.py +1 -1
- claude_mpm/services/diagnostics/checks/__init__.py +6 -6
- claude_mpm/services/diagnostics/checks/agent_check.py +152 -97
- claude_mpm/services/diagnostics/checks/base_check.py +12 -16
- claude_mpm/services/diagnostics/checks/claude_desktop_check.py +84 -81
- claude_mpm/services/diagnostics/checks/common_issues_check.py +99 -91
- claude_mpm/services/diagnostics/checks/configuration_check.py +82 -77
- claude_mpm/services/diagnostics/checks/filesystem_check.py +67 -68
- claude_mpm/services/diagnostics/checks/installation_check.py +254 -94
- claude_mpm/services/diagnostics/checks/mcp_check.py +90 -88
- claude_mpm/services/diagnostics/checks/monitor_check.py +75 -76
- claude_mpm/services/diagnostics/checks/startup_log_check.py +67 -73
- claude_mpm/services/diagnostics/diagnostic_runner.py +67 -59
- claude_mpm/services/diagnostics/doctor_reporter.py +107 -70
- claude_mpm/services/diagnostics/models.py +21 -19
- claude_mpm/services/event_aggregator.py +10 -17
- claude_mpm/services/event_bus/__init__.py +1 -1
- claude_mpm/services/event_bus/config.py +54 -35
- claude_mpm/services/event_bus/event_bus.py +76 -71
- claude_mpm/services/event_bus/relay.py +74 -64
- claude_mpm/services/events/__init__.py +11 -11
- claude_mpm/services/events/consumers/__init__.py +3 -3
- claude_mpm/services/events/consumers/dead_letter.py +71 -63
- claude_mpm/services/events/consumers/logging.py +39 -37
- claude_mpm/services/events/consumers/metrics.py +56 -57
- claude_mpm/services/events/consumers/socketio.py +82 -81
- claude_mpm/services/events/core.py +110 -99
- claude_mpm/services/events/interfaces.py +56 -72
- claude_mpm/services/events/producers/__init__.py +1 -1
- claude_mpm/services/events/producers/hook.py +38 -38
- claude_mpm/services/events/producers/system.py +46 -44
- claude_mpm/services/exceptions.py +81 -80
- claude_mpm/services/framework_claude_md_generator/__init__.py +2 -4
- claude_mpm/services/framework_claude_md_generator/content_assembler.py +3 -5
- claude_mpm/services/framework_claude_md_generator/content_validator.py +1 -1
- claude_mpm/services/framework_claude_md_generator/deployment_manager.py +4 -4
- claude_mpm/services/framework_claude_md_generator/section_generators/__init__.py +0 -1
- claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +0 -2
- claude_mpm/services/framework_claude_md_generator/version_manager.py +4 -5
- claude_mpm/services/hook_service.py +6 -9
- claude_mpm/services/infrastructure/__init__.py +1 -1
- claude_mpm/services/infrastructure/context_preservation.py +8 -12
- claude_mpm/services/infrastructure/monitoring.py +21 -23
- claude_mpm/services/mcp_gateway/__init__.py +37 -37
- claude_mpm/services/mcp_gateway/auto_configure.py +95 -103
- claude_mpm/services/mcp_gateway/config/__init__.py +1 -1
- claude_mpm/services/mcp_gateway/config/config_loader.py +23 -25
- claude_mpm/services/mcp_gateway/config/config_schema.py +5 -5
- claude_mpm/services/mcp_gateway/config/configuration.py +9 -6
- claude_mpm/services/mcp_gateway/core/__init__.py +10 -10
- claude_mpm/services/mcp_gateway/core/base.py +0 -3
- claude_mpm/services/mcp_gateway/core/interfaces.py +1 -38
- claude_mpm/services/mcp_gateway/core/process_pool.py +99 -93
- claude_mpm/services/mcp_gateway/core/singleton_manager.py +65 -62
- claude_mpm/services/mcp_gateway/core/startup_verification.py +75 -74
- claude_mpm/services/mcp_gateway/main.py +2 -1
- claude_mpm/services/mcp_gateway/registry/service_registry.py +5 -8
- claude_mpm/services/mcp_gateway/registry/tool_registry.py +1 -1
- claude_mpm/services/mcp_gateway/server/__init__.py +1 -1
- claude_mpm/services/mcp_gateway/server/mcp_gateway.py +12 -19
- claude_mpm/services/mcp_gateway/server/stdio_handler.py +4 -3
- claude_mpm/services/mcp_gateway/server/stdio_server.py +79 -71
- claude_mpm/services/mcp_gateway/tools/__init__.py +2 -2
- claude_mpm/services/mcp_gateway/tools/base_adapter.py +5 -6
- claude_mpm/services/mcp_gateway/tools/document_summarizer.py +13 -22
- claude_mpm/services/mcp_gateway/tools/health_check_tool.py +79 -78
- claude_mpm/services/mcp_gateway/tools/hello_world.py +12 -14
- claude_mpm/services/mcp_gateway/tools/ticket_tools.py +42 -49
- claude_mpm/services/mcp_gateway/tools/unified_ticket_tool.py +51 -55
- claude_mpm/services/memory/__init__.py +3 -3
- claude_mpm/services/memory/builder.py +3 -6
- claude_mpm/services/memory/cache/__init__.py +1 -1
- claude_mpm/services/memory/cache/shared_prompt_cache.py +3 -5
- claude_mpm/services/memory/cache/simple_cache.py +1 -1
- claude_mpm/services/memory/indexed_memory.py +5 -7
- claude_mpm/services/memory/optimizer.py +7 -10
- claude_mpm/services/memory/router.py +8 -9
- claude_mpm/services/memory_hook_service.py +48 -34
- claude_mpm/services/monitor_build_service.py +77 -73
- claude_mpm/services/port_manager.py +130 -108
- claude_mpm/services/project/analyzer.py +12 -10
- claude_mpm/services/project/registry.py +11 -11
- claude_mpm/services/recovery_manager.py +10 -19
- claude_mpm/services/response_tracker.py +0 -1
- claude_mpm/services/runner_configuration_service.py +19 -20
- claude_mpm/services/session_management_service.py +7 -11
- claude_mpm/services/shared/__init__.py +1 -1
- claude_mpm/services/shared/async_service_base.py +58 -50
- claude_mpm/services/shared/config_service_base.py +73 -67
- claude_mpm/services/shared/lifecycle_service_base.py +82 -78
- claude_mpm/services/shared/manager_base.py +94 -82
- claude_mpm/services/shared/service_factory.py +96 -98
- claude_mpm/services/socketio/__init__.py +3 -3
- claude_mpm/services/socketio/client_proxy.py +5 -5
- claude_mpm/services/socketio/event_normalizer.py +199 -181
- claude_mpm/services/socketio/handlers/__init__.py +3 -3
- claude_mpm/services/socketio/handlers/base.py +5 -4
- claude_mpm/services/socketio/handlers/connection.py +163 -136
- claude_mpm/services/socketio/handlers/file.py +13 -14
- claude_mpm/services/socketio/handlers/git.py +12 -7
- claude_mpm/services/socketio/handlers/hook.py +49 -44
- claude_mpm/services/socketio/handlers/memory.py +0 -1
- claude_mpm/services/socketio/handlers/project.py +0 -1
- claude_mpm/services/socketio/handlers/registry.py +37 -19
- claude_mpm/services/socketio/migration_utils.py +98 -84
- claude_mpm/services/socketio/server/__init__.py +1 -1
- claude_mpm/services/socketio/server/broadcaster.py +81 -87
- claude_mpm/services/socketio/server/core.py +65 -54
- claude_mpm/services/socketio/server/eventbus_integration.py +95 -56
- claude_mpm/services/socketio/server/main.py +64 -38
- claude_mpm/services/socketio_client_manager.py +10 -12
- claude_mpm/services/subprocess_launcher_service.py +4 -7
- claude_mpm/services/system_instructions_service.py +13 -14
- claude_mpm/services/ticket_manager.py +2 -2
- claude_mpm/services/utility_service.py +5 -13
- claude_mpm/services/version_control/__init__.py +16 -16
- claude_mpm/services/version_control/branch_strategy.py +5 -8
- claude_mpm/services/version_control/conflict_resolution.py +9 -23
- claude_mpm/services/version_control/git_operations.py +5 -7
- claude_mpm/services/version_control/semantic_versioning.py +16 -17
- claude_mpm/services/version_control/version_parser.py +13 -18
- claude_mpm/services/version_service.py +10 -11
- claude_mpm/storage/__init__.py +1 -1
- claude_mpm/storage/state_storage.py +22 -28
- claude_mpm/utils/__init__.py +6 -6
- claude_mpm/utils/agent_dependency_loader.py +47 -33
- claude_mpm/utils/config_manager.py +11 -14
- claude_mpm/utils/dependency_cache.py +1 -1
- claude_mpm/utils/dependency_manager.py +13 -17
- claude_mpm/utils/dependency_strategies.py +8 -10
- claude_mpm/utils/environment_context.py +3 -9
- claude_mpm/utils/error_handler.py +3 -13
- claude_mpm/utils/file_utils.py +1 -1
- claude_mpm/utils/path_operations.py +8 -12
- claude_mpm/utils/robust_installer.py +110 -33
- claude_mpm/utils/subprocess_utils.py +5 -6
- claude_mpm/validation/agent_validator.py +3 -6
- claude_mpm/validation/frontmatter_validator.py +1 -1
- {claude_mpm-4.1.1.dist-info → claude_mpm-4.1.3.dist-info}/METADATA +1 -1
- claude_mpm-4.1.3.dist-info/RECORD +528 -0
- claude_mpm/cli/commands/run_config_checker.py +0 -160
- claude_mpm-4.1.1.dist-info/RECORD +0 -494
- {claude_mpm-4.1.1.dist-info → claude_mpm-4.1.3.dist-info}/WHEEL +0 -0
- {claude_mpm-4.1.1.dist-info → claude_mpm-4.1.3.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.1.1.dist-info → claude_mpm-4.1.3.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.1.1.dist-info → claude_mpm-4.1.3.dist-info}/top_level.txt +0 -0
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
"""Framework loader for Claude MPM."""
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
-
import os
|
|
5
|
-
import sys
|
|
6
4
|
import time
|
|
7
5
|
from datetime import datetime
|
|
8
6
|
from pathlib import Path
|
|
9
|
-
from typing import Any, Dict, Optional
|
|
7
|
+
from typing import Any, Dict, Optional
|
|
10
8
|
|
|
11
9
|
# Import resource handling for packaged installations
|
|
12
10
|
try:
|
|
@@ -28,6 +26,32 @@ AgentRegistryAdapter = safe_import(
|
|
|
28
26
|
"claude_mpm.core.agent_registry", "core.agent_registry", ["AgentRegistryAdapter"]
|
|
29
27
|
)
|
|
30
28
|
|
|
29
|
+
# Import the service container and interfaces
|
|
30
|
+
try:
|
|
31
|
+
from claude_mpm.services.core.cache_manager import CacheManager
|
|
32
|
+
from claude_mpm.services.core.memory_manager import MemoryManager
|
|
33
|
+
from claude_mpm.services.core.path_resolver import PathResolver
|
|
34
|
+
from claude_mpm.services.core.service_container import (
|
|
35
|
+
ServiceContainer,
|
|
36
|
+
get_global_container,
|
|
37
|
+
)
|
|
38
|
+
from claude_mpm.services.core.service_interfaces import (
|
|
39
|
+
ICacheManager,
|
|
40
|
+
IMemoryManager,
|
|
41
|
+
IPathResolver,
|
|
42
|
+
)
|
|
43
|
+
except ImportError:
|
|
44
|
+
# Fallback for development environments
|
|
45
|
+
from ..services.core.cache_manager import CacheManager
|
|
46
|
+
from ..services.core.memory_manager import MemoryManager
|
|
47
|
+
from ..services.core.path_resolver import PathResolver
|
|
48
|
+
from ..services.core.service_container import ServiceContainer, get_global_container
|
|
49
|
+
from ..services.core.service_interfaces import (
|
|
50
|
+
ICacheManager,
|
|
51
|
+
IMemoryManager,
|
|
52
|
+
IPathResolver,
|
|
53
|
+
)
|
|
54
|
+
|
|
31
55
|
|
|
32
56
|
class FrameworkLoader:
|
|
33
57
|
"""
|
|
@@ -38,33 +62,33 @@ class FrameworkLoader:
|
|
|
38
62
|
2. Loading custom instructions from .claude-mpm/ directories
|
|
39
63
|
3. Preparing agent definitions
|
|
40
64
|
4. Formatting for injection
|
|
41
|
-
|
|
65
|
+
|
|
42
66
|
Custom Instructions Loading:
|
|
43
67
|
The framework loader supports custom instructions through .claude-mpm/ directories.
|
|
44
68
|
It NEVER reads from .claude/ directories to avoid conflicts with Claude Code.
|
|
45
|
-
|
|
69
|
+
|
|
46
70
|
File Loading Precedence (highest to lowest):
|
|
47
|
-
|
|
71
|
+
|
|
48
72
|
INSTRUCTIONS.md:
|
|
49
73
|
1. Project: ./.claude-mpm/INSTRUCTIONS.md
|
|
50
74
|
2. User: ~/.claude-mpm/INSTRUCTIONS.md
|
|
51
75
|
3. System: (built-in framework instructions)
|
|
52
|
-
|
|
76
|
+
|
|
53
77
|
WORKFLOW.md:
|
|
54
78
|
1. Project: ./.claude-mpm/WORKFLOW.md
|
|
55
79
|
2. User: ~/.claude-mpm/WORKFLOW.md
|
|
56
80
|
3. System: src/claude_mpm/agents/WORKFLOW.md
|
|
57
|
-
|
|
81
|
+
|
|
58
82
|
MEMORY.md:
|
|
59
83
|
1. Project: ./.claude-mpm/MEMORY.md
|
|
60
84
|
2. User: ~/.claude-mpm/MEMORY.md
|
|
61
85
|
3. System: src/claude_mpm/agents/MEMORY.md
|
|
62
|
-
|
|
86
|
+
|
|
63
87
|
Actual Memories:
|
|
64
88
|
- User: ~/.claude-mpm/memories/PM_memories.md
|
|
65
89
|
- Project: ./.claude-mpm/memories/PM_memories.md (overrides user)
|
|
66
90
|
- Agent memories: *_memories.md files (only loaded if agent is deployed)
|
|
67
|
-
|
|
91
|
+
|
|
68
92
|
Important Notes:
|
|
69
93
|
- Project-level files always override user-level files
|
|
70
94
|
- User-level files always override system defaults
|
|
@@ -73,7 +97,10 @@ class FrameworkLoader:
|
|
|
73
97
|
"""
|
|
74
98
|
|
|
75
99
|
def __init__(
|
|
76
|
-
self,
|
|
100
|
+
self,
|
|
101
|
+
framework_path: Optional[Path] = None,
|
|
102
|
+
agents_dir: Optional[Path] = None,
|
|
103
|
+
service_container: Optional[ServiceContainer] = None,
|
|
77
104
|
):
|
|
78
105
|
"""
|
|
79
106
|
Initialize framework loader.
|
|
@@ -81,241 +108,151 @@ class FrameworkLoader:
|
|
|
81
108
|
Args:
|
|
82
109
|
framework_path: Explicit path to framework (auto-detected if None)
|
|
83
110
|
agents_dir: Custom agents directory (overrides framework agents)
|
|
111
|
+
service_container: Optional service container for dependency injection
|
|
84
112
|
"""
|
|
85
113
|
self.logger = get_logger("framework_loader")
|
|
86
|
-
self.framework_path = framework_path or self._detect_framework_path()
|
|
87
114
|
self.agents_dir = agents_dir
|
|
88
115
|
self.framework_version = None
|
|
89
116
|
self.framework_last_modified = None
|
|
90
|
-
|
|
91
|
-
#
|
|
92
|
-
self.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
self.
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
self.
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
self.
|
|
105
|
-
|
|
117
|
+
|
|
118
|
+
# Use provided container or get global container
|
|
119
|
+
self.container = service_container or get_global_container()
|
|
120
|
+
|
|
121
|
+
# Register services if not already registered
|
|
122
|
+
if not self.container.is_registered(ICacheManager):
|
|
123
|
+
self.container.register(ICacheManager, CacheManager, True) # singleton=True
|
|
124
|
+
|
|
125
|
+
if not self.container.is_registered(IPathResolver):
|
|
126
|
+
# PathResolver depends on CacheManager, so resolve it first
|
|
127
|
+
cache_manager = self.container.resolve(ICacheManager)
|
|
128
|
+
path_resolver = PathResolver(cache_manager=cache_manager)
|
|
129
|
+
self.container.register_instance(IPathResolver, path_resolver)
|
|
130
|
+
|
|
131
|
+
if not self.container.is_registered(IMemoryManager):
|
|
132
|
+
# MemoryManager depends on both CacheManager and PathResolver
|
|
133
|
+
cache_manager = self.container.resolve(ICacheManager)
|
|
134
|
+
path_resolver = self.container.resolve(IPathResolver)
|
|
135
|
+
memory_manager = MemoryManager(
|
|
136
|
+
cache_manager=cache_manager, path_resolver=path_resolver
|
|
137
|
+
)
|
|
138
|
+
self.container.register_instance(IMemoryManager, memory_manager)
|
|
139
|
+
|
|
140
|
+
# Resolve services from container
|
|
141
|
+
self._cache_manager = self.container.resolve(ICacheManager)
|
|
142
|
+
self._path_resolver = self.container.resolve(IPathResolver)
|
|
143
|
+
self._memory_manager = self.container.resolve(IMemoryManager)
|
|
144
|
+
|
|
145
|
+
# Initialize framework path using PathResolver
|
|
146
|
+
self.framework_path = (
|
|
147
|
+
framework_path or self._path_resolver.detect_framework_path()
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
# Keep TTL constants for backward compatibility
|
|
151
|
+
# These are implementation-specific, so we use defaults if not available
|
|
152
|
+
if hasattr(self._cache_manager, "capabilities_ttl"):
|
|
153
|
+
self.CAPABILITIES_CACHE_TTL = self._cache_manager.capabilities_ttl
|
|
154
|
+
self.DEPLOYED_AGENTS_CACHE_TTL = self._cache_manager.deployed_agents_ttl
|
|
155
|
+
self.METADATA_CACHE_TTL = self._cache_manager.metadata_ttl
|
|
156
|
+
self.MEMORIES_CACHE_TTL = self._cache_manager.memories_ttl
|
|
157
|
+
else:
|
|
158
|
+
# Default TTL values
|
|
159
|
+
self.CAPABILITIES_CACHE_TTL = 60
|
|
160
|
+
self.DEPLOYED_AGENTS_CACHE_TTL = 30
|
|
161
|
+
self.METADATA_CACHE_TTL = 60
|
|
162
|
+
self.MEMORIES_CACHE_TTL = 60
|
|
163
|
+
|
|
106
164
|
self.framework_content = self._load_framework_content()
|
|
107
165
|
|
|
108
166
|
# Initialize agent registry
|
|
109
167
|
self.agent_registry = AgentRegistryAdapter(self.framework_path)
|
|
110
|
-
|
|
168
|
+
|
|
111
169
|
# Initialize output style manager (must be after content is loaded)
|
|
112
170
|
self.output_style_manager = None
|
|
113
171
|
# Defer initialization until first use to ensure content is loaded
|
|
114
|
-
|
|
172
|
+
|
|
115
173
|
def clear_all_caches(self) -> None:
|
|
116
174
|
"""Clear all caches to force reload on next access."""
|
|
117
|
-
self.
|
|
118
|
-
|
|
119
|
-
self._agent_capabilities_cache_time = 0
|
|
120
|
-
self._deployed_agents_cache = None
|
|
121
|
-
self._deployed_agents_cache_time = 0
|
|
122
|
-
self._agent_metadata_cache.clear()
|
|
123
|
-
self._memories_cache = None
|
|
124
|
-
self._memories_cache_time = 0
|
|
125
|
-
|
|
175
|
+
self._cache_manager.clear_all()
|
|
176
|
+
|
|
126
177
|
def clear_agent_caches(self) -> None:
|
|
127
178
|
"""Clear agent-related caches (capabilities, deployed agents, metadata)."""
|
|
128
|
-
self.
|
|
129
|
-
|
|
130
|
-
self._agent_capabilities_cache_time = 0
|
|
131
|
-
self._deployed_agents_cache = None
|
|
132
|
-
self._deployed_agents_cache_time = 0
|
|
133
|
-
self._agent_metadata_cache.clear()
|
|
134
|
-
|
|
179
|
+
self._cache_manager.clear_agent_caches()
|
|
180
|
+
|
|
135
181
|
def clear_memory_caches(self) -> None:
|
|
136
182
|
"""Clear memory-related caches."""
|
|
137
|
-
self.
|
|
138
|
-
self._memories_cache = None
|
|
139
|
-
self._memories_cache_time = 0
|
|
183
|
+
self._cache_manager.clear_memory_caches()
|
|
140
184
|
|
|
141
185
|
def _initialize_output_style(self) -> None:
|
|
142
186
|
"""Initialize output style management and deploy if applicable."""
|
|
143
187
|
try:
|
|
144
188
|
from claude_mpm.core.output_style_manager import OutputStyleManager
|
|
145
|
-
|
|
189
|
+
|
|
146
190
|
self.output_style_manager = OutputStyleManager()
|
|
147
|
-
|
|
191
|
+
|
|
148
192
|
# Log detailed output style status
|
|
149
193
|
self._log_output_style_status()
|
|
150
|
-
|
|
194
|
+
|
|
151
195
|
# Extract and save output style content (pass self to reuse loaded content)
|
|
152
|
-
output_style_content =
|
|
153
|
-
|
|
154
|
-
|
|
196
|
+
output_style_content = (
|
|
197
|
+
self.output_style_manager.extract_output_style_content(
|
|
198
|
+
framework_loader=self
|
|
199
|
+
)
|
|
200
|
+
)
|
|
201
|
+
self.output_style_manager.save_output_style(output_style_content)
|
|
202
|
+
|
|
155
203
|
# Deploy to Claude Code if supported
|
|
156
|
-
deployed = self.output_style_manager.deploy_output_style(
|
|
157
|
-
|
|
204
|
+
deployed = self.output_style_manager.deploy_output_style(
|
|
205
|
+
output_style_content
|
|
206
|
+
)
|
|
207
|
+
|
|
158
208
|
if deployed:
|
|
159
209
|
self.logger.info("✅ Output style deployed to Claude Code >= 1.0.83")
|
|
160
210
|
else:
|
|
161
|
-
self.logger.info(
|
|
162
|
-
|
|
211
|
+
self.logger.info(
|
|
212
|
+
"📝 Output style will be injected into instructions for older Claude versions"
|
|
213
|
+
)
|
|
214
|
+
|
|
163
215
|
except Exception as e:
|
|
164
216
|
self.logger.warning(f"❌ Failed to initialize output style manager: {e}")
|
|
165
217
|
# Continue without output style management
|
|
166
|
-
|
|
218
|
+
|
|
167
219
|
def _log_output_style_status(self) -> None:
|
|
168
220
|
"""Log comprehensive output style status information."""
|
|
169
221
|
if not self.output_style_manager:
|
|
170
222
|
return
|
|
171
|
-
|
|
223
|
+
|
|
172
224
|
# Claude version detection
|
|
173
225
|
claude_version = self.output_style_manager.claude_version
|
|
174
226
|
if claude_version:
|
|
175
227
|
self.logger.info(f"Claude Code version detected: {claude_version}")
|
|
176
|
-
|
|
228
|
+
|
|
177
229
|
# Check if version supports output styles
|
|
178
230
|
if self.output_style_manager.supports_output_styles():
|
|
179
231
|
self.logger.info("✅ Claude Code supports output styles (>= 1.0.83)")
|
|
180
|
-
|
|
232
|
+
|
|
181
233
|
# Check deployment status
|
|
182
234
|
output_style_path = self.output_style_manager.output_style_path
|
|
183
235
|
if output_style_path.exists():
|
|
184
|
-
self.logger.info(
|
|
236
|
+
self.logger.info(
|
|
237
|
+
f"📁 Output style file exists: {output_style_path}"
|
|
238
|
+
)
|
|
185
239
|
else:
|
|
186
|
-
self.logger.info(
|
|
187
|
-
|
|
240
|
+
self.logger.info(
|
|
241
|
+
f"📝 Output style will be created at: {output_style_path}"
|
|
242
|
+
)
|
|
243
|
+
|
|
188
244
|
else:
|
|
189
|
-
self.logger.info(
|
|
190
|
-
|
|
245
|
+
self.logger.info(
|
|
246
|
+
f"⚠️ Claude Code {claude_version} does not support output styles (< 1.0.83)"
|
|
247
|
+
)
|
|
248
|
+
self.logger.info(
|
|
249
|
+
"📝 Output style content will be injected into framework instructions"
|
|
250
|
+
)
|
|
191
251
|
else:
|
|
192
252
|
self.logger.info("⚠️ Claude Code not detected or version unknown")
|
|
193
|
-
self.logger.info(
|
|
194
|
-
|
|
195
|
-
def _detect_framework_path(self) -> Optional[Path]:
|
|
196
|
-
"""Auto-detect claude-mpm framework using unified path management."""
|
|
197
|
-
try:
|
|
198
|
-
# Use the unified path manager for consistent detection
|
|
199
|
-
from ..core.unified_paths import get_path_manager, DeploymentContext
|
|
200
|
-
|
|
201
|
-
path_manager = get_path_manager()
|
|
202
|
-
deployment_context = path_manager._deployment_context
|
|
203
|
-
|
|
204
|
-
# Check if we're in a packaged installation
|
|
205
|
-
if deployment_context in [DeploymentContext.PIP_INSTALL, DeploymentContext.PIPX_INSTALL, DeploymentContext.SYSTEM_PACKAGE]:
|
|
206
|
-
self.logger.info(f"Running from packaged installation (context: {deployment_context})")
|
|
207
|
-
# Return a marker path to indicate packaged installation
|
|
208
|
-
return Path("__PACKAGED__")
|
|
209
|
-
elif deployment_context == DeploymentContext.DEVELOPMENT:
|
|
210
|
-
# Development mode - use framework root
|
|
211
|
-
framework_root = path_manager.framework_root
|
|
212
|
-
if (framework_root / "src" / "claude_mpm" / "agents").exists():
|
|
213
|
-
self.logger.info(f"Using claude-mpm development installation at: {framework_root}")
|
|
214
|
-
return framework_root
|
|
215
|
-
elif deployment_context == DeploymentContext.EDITABLE_INSTALL:
|
|
216
|
-
# Editable install - similar to development
|
|
217
|
-
framework_root = path_manager.framework_root
|
|
218
|
-
if (framework_root / "src" / "claude_mpm" / "agents").exists():
|
|
219
|
-
self.logger.info(f"Using claude-mpm editable installation at: {framework_root}")
|
|
220
|
-
return framework_root
|
|
221
|
-
|
|
222
|
-
except Exception as e:
|
|
223
|
-
self.logger.warning(f"Failed to use unified path manager for framework detection: {e}")
|
|
224
|
-
# Fall back to original detection logic
|
|
225
|
-
pass
|
|
226
|
-
|
|
227
|
-
# Fallback: Original detection logic for compatibility
|
|
228
|
-
try:
|
|
229
|
-
# Check if the package is installed
|
|
230
|
-
import claude_mpm
|
|
231
|
-
package_file = Path(claude_mpm.__file__)
|
|
232
|
-
|
|
233
|
-
# For packaged installations, we don't need a framework path
|
|
234
|
-
# since we'll use importlib.resources to load files
|
|
235
|
-
if 'site-packages' in str(package_file) or 'dist-packages' in str(package_file):
|
|
236
|
-
self.logger.info(f"Running from packaged installation at: {package_file.parent}")
|
|
237
|
-
# Return a marker path to indicate packaged installation
|
|
238
|
-
return Path("__PACKAGED__")
|
|
239
|
-
except ImportError:
|
|
240
|
-
pass
|
|
241
|
-
|
|
242
|
-
# Then check if we're in claude-mpm project (development mode)
|
|
243
|
-
current_file = Path(__file__)
|
|
244
|
-
if "claude-mpm" in str(current_file):
|
|
245
|
-
# We're running from claude-mpm, use its agents
|
|
246
|
-
for parent in current_file.parents:
|
|
247
|
-
if parent.name == "claude-mpm":
|
|
248
|
-
if (parent / "src" / "claude_mpm" / "agents").exists():
|
|
249
|
-
self.logger.info(f"Using claude-mpm at: {parent}")
|
|
250
|
-
return parent
|
|
251
|
-
break
|
|
252
|
-
|
|
253
|
-
# Otherwise check common locations for claude-mpm
|
|
254
|
-
candidates = [
|
|
255
|
-
# Current directory (if we're already in claude-mpm)
|
|
256
|
-
Path.cwd(),
|
|
257
|
-
# Development location
|
|
258
|
-
Path.home() / "Projects" / "claude-mpm",
|
|
259
|
-
# Current directory subdirectory
|
|
260
|
-
Path.cwd() / "claude-mpm",
|
|
261
|
-
]
|
|
262
|
-
|
|
263
|
-
for candidate in candidates:
|
|
264
|
-
if candidate and candidate.exists():
|
|
265
|
-
# Check for claude-mpm agents directory
|
|
266
|
-
if (candidate / "src" / "claude_mpm" / "agents").exists():
|
|
267
|
-
self.logger.info(f"Found claude-mpm at: {candidate}")
|
|
268
|
-
return candidate
|
|
269
|
-
|
|
270
|
-
self.logger.warning("Framework not found, will use minimal instructions")
|
|
271
|
-
return None
|
|
272
|
-
|
|
273
|
-
def _get_npm_global_path(self) -> Optional[Path]:
|
|
274
|
-
"""Get npm global installation path."""
|
|
275
|
-
try:
|
|
276
|
-
import subprocess
|
|
277
|
-
|
|
278
|
-
result = subprocess.run(
|
|
279
|
-
["npm", "root", "-g"], capture_output=True, text=True, timeout=5
|
|
253
|
+
self.logger.info(
|
|
254
|
+
"📝 Output style content will be injected into framework instructions as fallback"
|
|
280
255
|
)
|
|
281
|
-
if result.returncode == 0:
|
|
282
|
-
npm_root = Path(result.stdout.strip())
|
|
283
|
-
return npm_root / "@bobmatnyc" / "claude-multiagent-pm"
|
|
284
|
-
except:
|
|
285
|
-
pass
|
|
286
|
-
return None
|
|
287
|
-
|
|
288
|
-
def _discover_framework_paths(
|
|
289
|
-
self,
|
|
290
|
-
) -> tuple[Optional[Path], Optional[Path], Optional[Path]]:
|
|
291
|
-
"""
|
|
292
|
-
Discover agent directories based on priority.
|
|
293
|
-
|
|
294
|
-
Returns:
|
|
295
|
-
Tuple of (agents_dir, templates_dir, main_dir)
|
|
296
|
-
"""
|
|
297
|
-
agents_dir = None
|
|
298
|
-
templates_dir = None
|
|
299
|
-
main_dir = None
|
|
300
|
-
|
|
301
|
-
if self.agents_dir and self.agents_dir.exists():
|
|
302
|
-
agents_dir = self.agents_dir
|
|
303
|
-
self.logger.info(f"Using custom agents directory: {agents_dir}")
|
|
304
|
-
elif self.framework_path and self.framework_path != Path("__PACKAGED__"):
|
|
305
|
-
# Prioritize templates directory over main agents directory
|
|
306
|
-
templates_dir = (
|
|
307
|
-
self.framework_path / "src" / "claude_mpm" / "agents" / "templates"
|
|
308
|
-
)
|
|
309
|
-
main_dir = self.framework_path / "src" / "claude_mpm" / "agents"
|
|
310
|
-
|
|
311
|
-
if templates_dir.exists() and any(templates_dir.glob("*.md")):
|
|
312
|
-
agents_dir = templates_dir
|
|
313
|
-
self.logger.info(f"Using agents from templates directory: {agents_dir}")
|
|
314
|
-
elif main_dir.exists() and any(main_dir.glob("*.md")):
|
|
315
|
-
agents_dir = main_dir
|
|
316
|
-
self.logger.info(f"Using agents from main directory: {agents_dir}")
|
|
317
|
-
|
|
318
|
-
return agents_dir, templates_dir, main_dir
|
|
319
256
|
|
|
320
257
|
def _try_load_file(self, file_path: Path, file_type: str) -> Optional[str]:
|
|
321
258
|
"""
|
|
@@ -361,29 +298,6 @@ class FrameworkLoader:
|
|
|
361
298
|
self.logger.error(f"Failed to load {file_type}: {e}")
|
|
362
299
|
return None
|
|
363
300
|
|
|
364
|
-
def _migrate_memory_file(self, old_path: Path, new_path: Path) -> None:
|
|
365
|
-
"""
|
|
366
|
-
Migrate memory file from old naming convention to new.
|
|
367
|
-
|
|
368
|
-
WHY: Supports backward compatibility by automatically migrating from
|
|
369
|
-
the old {agent_id}_agent.md and {agent_id}.md formats to the new {agent_id}_memories.md format.
|
|
370
|
-
|
|
371
|
-
Args:
|
|
372
|
-
old_path: Path to the old file
|
|
373
|
-
new_path: Path to the new file
|
|
374
|
-
"""
|
|
375
|
-
if old_path.exists() and not new_path.exists():
|
|
376
|
-
try:
|
|
377
|
-
# Read content from old file
|
|
378
|
-
content = old_path.read_text(encoding="utf-8")
|
|
379
|
-
# Write to new file
|
|
380
|
-
new_path.write_text(content, encoding="utf-8")
|
|
381
|
-
# Remove old file
|
|
382
|
-
old_path.unlink()
|
|
383
|
-
self.logger.info(f"Migrated memory file from {old_path.name} to {new_path.name}")
|
|
384
|
-
except Exception as e:
|
|
385
|
-
self.logger.error(f"Failed to migrate memory file {old_path.name}: {e}")
|
|
386
|
-
|
|
387
301
|
def _load_instructions_file(self, content: Dict[str, Any]) -> None:
|
|
388
302
|
"""
|
|
389
303
|
Load custom INSTRUCTIONS.md from .claude-mpm directories.
|
|
@@ -391,7 +305,7 @@ class FrameworkLoader:
|
|
|
391
305
|
Precedence (highest to lowest):
|
|
392
306
|
1. Project-specific: ./.claude-mpm/INSTRUCTIONS.md
|
|
393
307
|
2. User-specific: ~/.claude-mpm/INSTRUCTIONS.md
|
|
394
|
-
|
|
308
|
+
|
|
395
309
|
NOTE: We do NOT load CLAUDE.md files since Claude Code already picks them up automatically.
|
|
396
310
|
This prevents duplication of instructions.
|
|
397
311
|
|
|
@@ -407,9 +321,11 @@ class FrameworkLoader:
|
|
|
407
321
|
if loaded_content:
|
|
408
322
|
content["custom_instructions"] = loaded_content
|
|
409
323
|
content["custom_instructions_level"] = "project"
|
|
410
|
-
self.logger.info(
|
|
324
|
+
self.logger.info(
|
|
325
|
+
"Using project-specific PM instructions from .claude-mpm/INSTRUCTIONS.md"
|
|
326
|
+
)
|
|
411
327
|
return
|
|
412
|
-
|
|
328
|
+
|
|
413
329
|
# Check for user-specific INSTRUCTIONS.md
|
|
414
330
|
user_instructions_path = Path.home() / ".claude-mpm" / "INSTRUCTIONS.md"
|
|
415
331
|
if user_instructions_path.exists():
|
|
@@ -419,7 +335,9 @@ class FrameworkLoader:
|
|
|
419
335
|
if loaded_content:
|
|
420
336
|
content["custom_instructions"] = loaded_content
|
|
421
337
|
content["custom_instructions_level"] = "user"
|
|
422
|
-
self.logger.info(
|
|
338
|
+
self.logger.info(
|
|
339
|
+
"Using user-specific PM instructions from ~/.claude-mpm/INSTRUCTIONS.md"
|
|
340
|
+
)
|
|
423
341
|
return
|
|
424
342
|
|
|
425
343
|
def _load_workflow_instructions(self, content: Dict[str, Any]) -> None:
|
|
@@ -430,7 +348,7 @@ class FrameworkLoader:
|
|
|
430
348
|
1. Project-specific: ./.claude-mpm/WORKFLOW.md
|
|
431
349
|
2. User-specific: ~/.claude-mpm/WORKFLOW.md
|
|
432
350
|
3. System default: src/claude_mpm/agents/WORKFLOW.md or packaged
|
|
433
|
-
|
|
351
|
+
|
|
434
352
|
NOTE: We do NOT load from .claude/ directories to avoid conflicts.
|
|
435
353
|
|
|
436
354
|
Args:
|
|
@@ -445,9 +363,11 @@ class FrameworkLoader:
|
|
|
445
363
|
if loaded_content:
|
|
446
364
|
content["workflow_instructions"] = loaded_content
|
|
447
365
|
content["workflow_instructions_level"] = "project"
|
|
448
|
-
self.logger.info(
|
|
366
|
+
self.logger.info(
|
|
367
|
+
"Using project-specific workflow instructions from .claude-mpm/WORKFLOW.md"
|
|
368
|
+
)
|
|
449
369
|
return
|
|
450
|
-
|
|
370
|
+
|
|
451
371
|
# Check for user-specific WORKFLOW.md (medium priority)
|
|
452
372
|
user_workflow_path = Path.home() / ".claude-mpm" / "WORKFLOW.md"
|
|
453
373
|
if user_workflow_path.exists():
|
|
@@ -457,7 +377,9 @@ class FrameworkLoader:
|
|
|
457
377
|
if loaded_content:
|
|
458
378
|
content["workflow_instructions"] = loaded_content
|
|
459
379
|
content["workflow_instructions_level"] = "user"
|
|
460
|
-
self.logger.info(
|
|
380
|
+
self.logger.info(
|
|
381
|
+
"Using user-specific workflow instructions from ~/.claude-mpm/WORKFLOW.md"
|
|
382
|
+
)
|
|
461
383
|
return
|
|
462
384
|
|
|
463
385
|
# Fall back to system workflow (lowest priority)
|
|
@@ -482,7 +404,7 @@ class FrameworkLoader:
|
|
|
482
404
|
1. Project-specific: ./.claude-mpm/MEMORY.md
|
|
483
405
|
2. User-specific: ~/.claude-mpm/MEMORY.md
|
|
484
406
|
3. System default: src/claude_mpm/agents/MEMORY.md or packaged
|
|
485
|
-
|
|
407
|
+
|
|
486
408
|
NOTE: We do NOT load from .claude/ directories to avoid conflicts.
|
|
487
409
|
|
|
488
410
|
Args:
|
|
@@ -497,9 +419,11 @@ class FrameworkLoader:
|
|
|
497
419
|
if loaded_content:
|
|
498
420
|
content["memory_instructions"] = loaded_content
|
|
499
421
|
content["memory_instructions_level"] = "project"
|
|
500
|
-
self.logger.info(
|
|
422
|
+
self.logger.info(
|
|
423
|
+
"Using project-specific memory instructions from .claude-mpm/MEMORY.md"
|
|
424
|
+
)
|
|
501
425
|
return
|
|
502
|
-
|
|
426
|
+
|
|
503
427
|
# Check for user-specific MEMORY.md (medium priority)
|
|
504
428
|
user_memory_path = Path.home() / ".claude-mpm" / "MEMORY.md"
|
|
505
429
|
if user_memory_path.exists():
|
|
@@ -509,7 +433,9 @@ class FrameworkLoader:
|
|
|
509
433
|
if loaded_content:
|
|
510
434
|
content["memory_instructions"] = loaded_content
|
|
511
435
|
content["memory_instructions_level"] = "user"
|
|
512
|
-
self.logger.info(
|
|
436
|
+
self.logger.info(
|
|
437
|
+
"Using user-specific memory instructions from ~/.claude-mpm/MEMORY.md"
|
|
438
|
+
)
|
|
513
439
|
return
|
|
514
440
|
|
|
515
441
|
# Fall back to system memory instructions (lowest priority)
|
|
@@ -525,357 +451,65 @@ class FrameworkLoader:
|
|
|
525
451
|
content["memory_instructions"] = loaded_content
|
|
526
452
|
content["memory_instructions_level"] = "system"
|
|
527
453
|
self.logger.info("Using system memory instructions")
|
|
528
|
-
|
|
454
|
+
|
|
529
455
|
def _get_deployed_agents(self) -> set:
|
|
530
456
|
"""
|
|
531
457
|
Get a set of deployed agent names from .claude/agents/ directories.
|
|
532
458
|
Uses caching to avoid repeated filesystem scans.
|
|
533
|
-
|
|
459
|
+
|
|
534
460
|
Returns:
|
|
535
461
|
Set of agent names (file stems) that are deployed
|
|
536
462
|
"""
|
|
537
|
-
#
|
|
538
|
-
|
|
539
|
-
if
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
return self._deployed_agents_cache
|
|
543
|
-
|
|
463
|
+
# Try to get from cache first
|
|
464
|
+
cached = self._cache_manager.get_deployed_agents()
|
|
465
|
+
if cached is not None:
|
|
466
|
+
return cached
|
|
467
|
+
|
|
544
468
|
# Cache miss or expired - perform actual scan
|
|
545
469
|
self.logger.debug("Scanning for deployed agents (cache miss or expired)")
|
|
546
470
|
deployed = set()
|
|
547
|
-
|
|
471
|
+
|
|
548
472
|
# Check multiple locations for deployed agents
|
|
549
473
|
agents_dirs = [
|
|
550
474
|
Path.cwd() / ".claude" / "agents", # Project-specific agents
|
|
551
475
|
Path.home() / ".claude" / "agents", # User's system agents
|
|
552
476
|
]
|
|
553
|
-
|
|
477
|
+
|
|
554
478
|
for agents_dir in agents_dirs:
|
|
555
479
|
if agents_dir.exists():
|
|
556
480
|
for agent_file in agents_dir.glob("*.md"):
|
|
557
481
|
if not agent_file.name.startswith("."):
|
|
558
482
|
# Use stem to get agent name without extension
|
|
559
483
|
deployed.add(agent_file.stem)
|
|
560
|
-
self.logger.debug(
|
|
561
|
-
|
|
484
|
+
self.logger.debug(
|
|
485
|
+
f"Found deployed agent: {agent_file.stem} in {agents_dir}"
|
|
486
|
+
)
|
|
487
|
+
|
|
562
488
|
self.logger.debug(f"Total deployed agents found: {len(deployed)}")
|
|
563
|
-
|
|
489
|
+
|
|
564
490
|
# Update cache
|
|
565
|
-
self.
|
|
566
|
-
|
|
567
|
-
|
|
491
|
+
self._cache_manager.set_deployed_agents(deployed)
|
|
492
|
+
|
|
568
493
|
return deployed
|
|
569
|
-
|
|
494
|
+
|
|
570
495
|
def _load_actual_memories(self, content: Dict[str, Any]) -> None:
|
|
571
496
|
"""
|
|
572
|
-
Load actual memories
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
2. Project-level memories from ./.claude-mpm/memories/ (overrides user)
|
|
578
|
-
|
|
579
|
-
This loads:
|
|
580
|
-
1. PM memories from PM_memories.md (always loaded)
|
|
581
|
-
2. Agent memories from <agent>_memories.md (only if agent is deployed)
|
|
582
|
-
|
|
497
|
+
Load actual memories using the MemoryManager service.
|
|
498
|
+
|
|
499
|
+
This method delegates all memory loading operations to the MemoryManager,
|
|
500
|
+
which handles caching, aggregation, deduplication, and legacy format migration.
|
|
501
|
+
|
|
583
502
|
Args:
|
|
584
503
|
content: Dictionary to update with actual memories
|
|
585
504
|
"""
|
|
586
|
-
#
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
if "actual_memories" in self._memories_cache:
|
|
595
|
-
content["actual_memories"] = self._memories_cache["actual_memories"]
|
|
596
|
-
if "agent_memories" in self._memories_cache:
|
|
597
|
-
content["agent_memories"] = self._memories_cache["agent_memories"]
|
|
598
|
-
return
|
|
599
|
-
|
|
600
|
-
# Cache miss or expired - perform actual loading
|
|
601
|
-
self.logger.debug("Loading memories from disk (cache miss or expired)")
|
|
602
|
-
|
|
603
|
-
# Define memory directories in priority order (user first, then project)
|
|
604
|
-
user_memories_dir = Path.home() / ".claude-mpm" / "memories"
|
|
605
|
-
project_memories_dir = Path.cwd() / ".claude-mpm" / "memories"
|
|
606
|
-
|
|
607
|
-
# Check for deployed agents
|
|
608
|
-
deployed_agents = self._get_deployed_agents()
|
|
609
|
-
|
|
610
|
-
# Track loading statistics
|
|
611
|
-
loaded_count = 0
|
|
612
|
-
skipped_count = 0
|
|
613
|
-
|
|
614
|
-
# Dictionary to store aggregated memories
|
|
615
|
-
pm_memories = []
|
|
616
|
-
agent_memories_dict = {}
|
|
617
|
-
|
|
618
|
-
# Load memories from user directory first
|
|
619
|
-
if user_memories_dir.exists():
|
|
620
|
-
self.logger.info(f"Loading user-level memory files from: {user_memories_dir}")
|
|
621
|
-
loaded, skipped = self._load_memories_from_directory(
|
|
622
|
-
user_memories_dir, deployed_agents, pm_memories, agent_memories_dict, "user"
|
|
623
|
-
)
|
|
624
|
-
loaded_count += loaded
|
|
625
|
-
skipped_count += skipped
|
|
626
|
-
else:
|
|
627
|
-
self.logger.debug(f"No user memories directory found at: {user_memories_dir}")
|
|
628
|
-
|
|
629
|
-
# Load memories from project directory (overrides user memories)
|
|
630
|
-
if project_memories_dir.exists():
|
|
631
|
-
self.logger.info(f"Loading project-level memory files from: {project_memories_dir}")
|
|
632
|
-
loaded, skipped = self._load_memories_from_directory(
|
|
633
|
-
project_memories_dir, deployed_agents, pm_memories, agent_memories_dict, "project"
|
|
634
|
-
)
|
|
635
|
-
loaded_count += loaded
|
|
636
|
-
skipped_count += skipped
|
|
637
|
-
else:
|
|
638
|
-
self.logger.debug(f"No project memories directory found at: {project_memories_dir}")
|
|
639
|
-
|
|
640
|
-
# Aggregate PM memories
|
|
641
|
-
if pm_memories:
|
|
642
|
-
aggregated_pm = self._aggregate_memories(pm_memories)
|
|
643
|
-
content["actual_memories"] = aggregated_pm
|
|
644
|
-
memory_size = len(aggregated_pm.encode('utf-8'))
|
|
645
|
-
self.logger.info(f"Aggregated PM memory ({memory_size:,} bytes) from {len(pm_memories)} source(s)")
|
|
646
|
-
|
|
647
|
-
# Store agent memories (already aggregated per agent)
|
|
648
|
-
if agent_memories_dict:
|
|
649
|
-
content["agent_memories"] = agent_memories_dict
|
|
650
|
-
for agent_name, memory_content in agent_memories_dict.items():
|
|
651
|
-
memory_size = len(memory_content.encode('utf-8'))
|
|
652
|
-
self.logger.debug(f"Aggregated {agent_name} memory: {memory_size:,} bytes")
|
|
653
|
-
|
|
654
|
-
# Update cache with loaded memories
|
|
655
|
-
self._memories_cache = {}
|
|
656
|
-
if "actual_memories" in content:
|
|
657
|
-
self._memories_cache["actual_memories"] = content["actual_memories"]
|
|
658
|
-
if "agent_memories" in content:
|
|
659
|
-
self._memories_cache["agent_memories"] = content["agent_memories"]
|
|
660
|
-
self._memories_cache_time = current_time
|
|
661
|
-
|
|
662
|
-
# Log detailed summary
|
|
663
|
-
if loaded_count > 0 or skipped_count > 0:
|
|
664
|
-
# Count unique agents with memories
|
|
665
|
-
agent_count = len(agent_memories_dict) if agent_memories_dict else 0
|
|
666
|
-
pm_loaded = bool(content.get("actual_memories"))
|
|
667
|
-
|
|
668
|
-
summary_parts = []
|
|
669
|
-
if pm_loaded:
|
|
670
|
-
summary_parts.append("PM memory loaded")
|
|
671
|
-
if agent_count > 0:
|
|
672
|
-
summary_parts.append(f"{agent_count} agent memories loaded")
|
|
673
|
-
if skipped_count > 0:
|
|
674
|
-
summary_parts.append(f"{skipped_count} non-deployed agent memories skipped")
|
|
675
|
-
|
|
676
|
-
self.logger.info(f"Memory loading complete: {' | '.join(summary_parts)}")
|
|
677
|
-
|
|
678
|
-
# Log deployed agents for reference
|
|
679
|
-
if len(deployed_agents) > 0:
|
|
680
|
-
self.logger.debug(f"Deployed agents available for memory loading: {', '.join(sorted(deployed_agents))}")
|
|
681
|
-
|
|
682
|
-
def _load_memories_from_directory(
|
|
683
|
-
self,
|
|
684
|
-
memories_dir: Path,
|
|
685
|
-
deployed_agents: set,
|
|
686
|
-
pm_memories: list,
|
|
687
|
-
agent_memories_dict: dict,
|
|
688
|
-
source: str
|
|
689
|
-
) -> tuple[int, int]:
|
|
690
|
-
"""
|
|
691
|
-
Load memories from a specific directory.
|
|
692
|
-
|
|
693
|
-
Args:
|
|
694
|
-
memories_dir: Directory to load memories from
|
|
695
|
-
deployed_agents: Set of deployed agent names
|
|
696
|
-
pm_memories: List to append PM memories to
|
|
697
|
-
agent_memories_dict: Dict to store agent memories
|
|
698
|
-
source: Source label ("user" or "project")
|
|
699
|
-
|
|
700
|
-
Returns:
|
|
701
|
-
Tuple of (loaded_count, skipped_count)
|
|
702
|
-
"""
|
|
703
|
-
loaded_count = 0
|
|
704
|
-
skipped_count = 0
|
|
705
|
-
|
|
706
|
-
# Load PM memories (always loaded)
|
|
707
|
-
# Support migration from both old formats
|
|
708
|
-
pm_memory_path = memories_dir / "PM_memories.md"
|
|
709
|
-
old_pm_path = memories_dir / "PM.md"
|
|
710
|
-
|
|
711
|
-
# Migrate from old PM.md if needed
|
|
712
|
-
if not pm_memory_path.exists() and old_pm_path.exists():
|
|
713
|
-
try:
|
|
714
|
-
old_pm_path.rename(pm_memory_path)
|
|
715
|
-
self.logger.info(f"Migrated PM.md to PM_memories.md")
|
|
716
|
-
except Exception as e:
|
|
717
|
-
self.logger.error(f"Failed to migrate PM.md: {e}")
|
|
718
|
-
pm_memory_path = old_pm_path # Fall back to old path
|
|
719
|
-
if pm_memory_path.exists():
|
|
720
|
-
loaded_content = self._try_load_file(
|
|
721
|
-
pm_memory_path, f"PM memory ({source})"
|
|
722
|
-
)
|
|
723
|
-
if loaded_content:
|
|
724
|
-
pm_memories.append({
|
|
725
|
-
"source": source,
|
|
726
|
-
"content": loaded_content,
|
|
727
|
-
"path": pm_memory_path
|
|
728
|
-
})
|
|
729
|
-
memory_size = len(loaded_content.encode('utf-8'))
|
|
730
|
-
self.logger.info(f"Loaded {source} PM memory: {pm_memory_path} ({memory_size:,} bytes)")
|
|
731
|
-
loaded_count += 1
|
|
732
|
-
|
|
733
|
-
# First, migrate any old format memory files to new format
|
|
734
|
-
# This handles backward compatibility for existing installations
|
|
735
|
-
for old_file in memories_dir.glob("*.md"):
|
|
736
|
-
# Skip files already in correct format and special files
|
|
737
|
-
if old_file.name.endswith("_memories.md") or old_file.name in ["PM.md", "README.md"]:
|
|
738
|
-
continue
|
|
739
|
-
|
|
740
|
-
# Determine new name based on old format
|
|
741
|
-
if old_file.stem.endswith("_agent"):
|
|
742
|
-
# Old format: {agent_name}_agent.md -> {agent_name}_memories.md
|
|
743
|
-
agent_name = old_file.stem[:-6] # Remove "_agent" suffix
|
|
744
|
-
new_path = memories_dir / f"{agent_name}_memories.md"
|
|
745
|
-
if not new_path.exists():
|
|
746
|
-
self._migrate_memory_file(old_file, new_path)
|
|
747
|
-
else:
|
|
748
|
-
# Intermediate format: {agent_name}.md -> {agent_name}_memories.md
|
|
749
|
-
agent_name = old_file.stem
|
|
750
|
-
new_path = memories_dir / f"{agent_name}_memories.md"
|
|
751
|
-
if not new_path.exists():
|
|
752
|
-
self._migrate_memory_file(old_file, new_path)
|
|
753
|
-
|
|
754
|
-
# Load agent memories (only for deployed agents)
|
|
755
|
-
# Only process *_memories.md files to avoid README.md and other docs
|
|
756
|
-
for memory_file in memories_dir.glob("*_memories.md"):
|
|
757
|
-
# Skip PM_memories.md as we already handled it
|
|
758
|
-
if memory_file.name == "PM_memories.md":
|
|
759
|
-
continue
|
|
760
|
-
|
|
761
|
-
# Extract agent name from file (remove "_memories" suffix)
|
|
762
|
-
agent_name = memory_file.stem[:-9] # Remove "_memories" suffix
|
|
763
|
-
|
|
764
|
-
# Check if agent is deployed
|
|
765
|
-
if agent_name in deployed_agents:
|
|
766
|
-
loaded_content = self._try_load_file(
|
|
767
|
-
memory_file, f"agent memory: {agent_name} ({source})"
|
|
768
|
-
)
|
|
769
|
-
if loaded_content:
|
|
770
|
-
# Store or merge agent memories
|
|
771
|
-
if agent_name not in agent_memories_dict:
|
|
772
|
-
agent_memories_dict[agent_name] = []
|
|
773
|
-
|
|
774
|
-
# If it's a list, append the new memory entry
|
|
775
|
-
if isinstance(agent_memories_dict[agent_name], list):
|
|
776
|
-
agent_memories_dict[agent_name].append({
|
|
777
|
-
"source": source,
|
|
778
|
-
"content": loaded_content,
|
|
779
|
-
"path": memory_file
|
|
780
|
-
})
|
|
781
|
-
|
|
782
|
-
memory_size = len(loaded_content.encode('utf-8'))
|
|
783
|
-
self.logger.info(f"Loaded {source} memory for {agent_name}: {memory_file.name} ({memory_size:,} bytes)")
|
|
784
|
-
loaded_count += 1
|
|
785
|
-
else:
|
|
786
|
-
# Provide more detailed logging about why the memory was skipped
|
|
787
|
-
self.logger.info(f"Skipped {source} memory: {memory_file.name} (agent '{agent_name}' not deployed)")
|
|
788
|
-
# Also log a debug message with available agents for diagnostics
|
|
789
|
-
if agent_name.replace('_', '-') in deployed_agents or agent_name.replace('-', '_') in deployed_agents:
|
|
790
|
-
# Detect naming mismatches
|
|
791
|
-
alt_name = agent_name.replace('_', '-') if '_' in agent_name else agent_name.replace('-', '_')
|
|
792
|
-
if alt_name in deployed_agents:
|
|
793
|
-
self.logger.warning(
|
|
794
|
-
f"Naming mismatch detected: Memory file uses '{agent_name}' but deployed agent is '{alt_name}'. "
|
|
795
|
-
f"Consider renaming {memory_file.name} to {alt_name}_memories.md"
|
|
796
|
-
)
|
|
797
|
-
skipped_count += 1
|
|
798
|
-
|
|
799
|
-
# After loading all memories for this directory, aggregate agent memories
|
|
800
|
-
for agent_name in list(agent_memories_dict.keys()):
|
|
801
|
-
if isinstance(agent_memories_dict[agent_name], list) and agent_memories_dict[agent_name]:
|
|
802
|
-
# Aggregate memories for this agent
|
|
803
|
-
aggregated = self._aggregate_memories(agent_memories_dict[agent_name])
|
|
804
|
-
agent_memories_dict[agent_name] = aggregated
|
|
805
|
-
|
|
806
|
-
return loaded_count, skipped_count
|
|
807
|
-
|
|
808
|
-
def _aggregate_memories(self, memory_entries: list) -> str:
|
|
809
|
-
"""
|
|
810
|
-
Aggregate multiple memory entries into a single memory string.
|
|
811
|
-
|
|
812
|
-
Strategy:
|
|
813
|
-
- Simplified to support list-based memories only
|
|
814
|
-
- Preserve all unique bullet-point items (lines starting with -)
|
|
815
|
-
- Remove exact duplicates
|
|
816
|
-
- Project-level memories take precedence over user-level
|
|
817
|
-
|
|
818
|
-
Args:
|
|
819
|
-
memory_entries: List of memory entries with source, content, and path
|
|
820
|
-
|
|
821
|
-
Returns:
|
|
822
|
-
Aggregated memory content as a string
|
|
823
|
-
"""
|
|
824
|
-
if not memory_entries:
|
|
825
|
-
return ""
|
|
826
|
-
|
|
827
|
-
# If only one entry, return it as-is
|
|
828
|
-
if len(memory_entries) == 1:
|
|
829
|
-
return memory_entries[0]["content"]
|
|
830
|
-
|
|
831
|
-
# Parse all memories into a simple list
|
|
832
|
-
all_items = {} # Dict to track items and their source
|
|
833
|
-
metadata_lines = []
|
|
834
|
-
agent_id = None
|
|
835
|
-
|
|
836
|
-
for entry in memory_entries:
|
|
837
|
-
content = entry["content"]
|
|
838
|
-
source = entry["source"]
|
|
839
|
-
|
|
840
|
-
for line in content.split('\n'):
|
|
841
|
-
# Check for header to extract agent_id
|
|
842
|
-
if line.startswith('# Agent Memory:'):
|
|
843
|
-
agent_id = line.replace('# Agent Memory:', '').strip()
|
|
844
|
-
# Check for metadata lines
|
|
845
|
-
elif line.startswith('<!-- ') and line.endswith(' -->'):
|
|
846
|
-
# Only keep metadata from project source or if not already present
|
|
847
|
-
if source == "project" or line not in metadata_lines:
|
|
848
|
-
metadata_lines.append(line)
|
|
849
|
-
# Check for list items
|
|
850
|
-
elif line.strip().startswith('-'):
|
|
851
|
-
# Normalize the item for comparison
|
|
852
|
-
item_text = line.strip()
|
|
853
|
-
normalized = item_text.lstrip('- ').strip().lower()
|
|
854
|
-
|
|
855
|
-
# Add item if new or if project source overrides user source
|
|
856
|
-
if normalized not in all_items or source == "project":
|
|
857
|
-
all_items[normalized] = (item_text, source)
|
|
858
|
-
|
|
859
|
-
# Build aggregated content as simple list
|
|
860
|
-
lines = []
|
|
861
|
-
|
|
862
|
-
# Add header
|
|
863
|
-
if agent_id:
|
|
864
|
-
lines.append(f"# Agent Memory: {agent_id}")
|
|
865
|
-
else:
|
|
866
|
-
lines.append("# Agent Memory")
|
|
867
|
-
|
|
868
|
-
# Add latest timestamp from metadata
|
|
869
|
-
from datetime import datetime
|
|
870
|
-
lines.append(f"<!-- Last Updated: {datetime.now().isoformat()}Z -->")
|
|
871
|
-
lines.append("")
|
|
872
|
-
|
|
873
|
-
# Add all unique items (sorted for consistency)
|
|
874
|
-
for normalized_key in sorted(all_items.keys()):
|
|
875
|
-
item_text, source = all_items[normalized_key]
|
|
876
|
-
lines.append(item_text)
|
|
877
|
-
|
|
878
|
-
return '\n'.join(lines)
|
|
505
|
+
# Use MemoryManager to load all memories
|
|
506
|
+
memories = self._memory_manager.load_memories()
|
|
507
|
+
|
|
508
|
+
# Apply loaded memories to content
|
|
509
|
+
if "actual_memories" in memories:
|
|
510
|
+
content["actual_memories"] = memories["actual_memories"]
|
|
511
|
+
if "agent_memories" in memories:
|
|
512
|
+
content["agent_memories"] = memories["agent_memories"]
|
|
879
513
|
|
|
880
514
|
def _load_single_agent(
|
|
881
515
|
self, agent_file: Path
|
|
@@ -972,7 +606,7 @@ class FrameworkLoader:
|
|
|
972
606
|
|
|
973
607
|
if not self.framework_path:
|
|
974
608
|
return content
|
|
975
|
-
|
|
609
|
+
|
|
976
610
|
# Check if this is a packaged installation
|
|
977
611
|
if self.framework_path == Path("__PACKAGED__"):
|
|
978
612
|
# Load files using importlib.resources for packaged installations
|
|
@@ -981,7 +615,11 @@ class FrameworkLoader:
|
|
|
981
615
|
# Load from filesystem for development mode
|
|
982
616
|
# Load framework's INSTRUCTIONS.md
|
|
983
617
|
framework_instructions_path = (
|
|
984
|
-
self.framework_path
|
|
618
|
+
self.framework_path
|
|
619
|
+
/ "src"
|
|
620
|
+
/ "claude_mpm"
|
|
621
|
+
/ "agents"
|
|
622
|
+
/ "INSTRUCTIONS.md"
|
|
985
623
|
)
|
|
986
624
|
if framework_instructions_path.exists():
|
|
987
625
|
loaded_content = self._try_load_file(
|
|
@@ -993,12 +631,14 @@ class FrameworkLoader:
|
|
|
993
631
|
# Add framework version to content
|
|
994
632
|
if self.framework_version:
|
|
995
633
|
content["instructions_version"] = self.framework_version
|
|
996
|
-
content[
|
|
997
|
-
|
|
998
|
-
|
|
634
|
+
content["version"] = (
|
|
635
|
+
self.framework_version
|
|
636
|
+
) # Update main version key
|
|
999
637
|
# Add modification timestamp to content
|
|
1000
638
|
if self.framework_last_modified:
|
|
1001
|
-
content["instructions_last_modified"] =
|
|
639
|
+
content["instructions_last_modified"] = (
|
|
640
|
+
self.framework_last_modified
|
|
641
|
+
)
|
|
1002
642
|
|
|
1003
643
|
# Load BASE_PM.md for core framework requirements
|
|
1004
644
|
base_pm_path = (
|
|
@@ -1016,33 +656,40 @@ class FrameworkLoader:
|
|
|
1016
656
|
|
|
1017
657
|
# Load MEMORY.md - check for project-specific first, then system
|
|
1018
658
|
self._load_memory_instructions(content)
|
|
1019
|
-
|
|
659
|
+
|
|
1020
660
|
# Load actual memories from .claude-mpm/memories/PM_memories.md
|
|
1021
661
|
self._load_actual_memories(content)
|
|
1022
662
|
|
|
1023
|
-
# Discover agent directories
|
|
1024
|
-
agents_dir, templates_dir, main_dir = self.
|
|
663
|
+
# Discover agent directories using PathResolver
|
|
664
|
+
agents_dir, templates_dir, main_dir = self._path_resolver.discover_agent_paths(
|
|
665
|
+
agents_dir=self.agents_dir, framework_path=self.framework_path
|
|
666
|
+
)
|
|
1025
667
|
|
|
1026
668
|
# Load agents from discovered directory
|
|
1027
669
|
self._load_agents_directory(content, agents_dir, templates_dir, main_dir)
|
|
1028
670
|
|
|
1029
671
|
return content
|
|
1030
|
-
|
|
672
|
+
|
|
1031
673
|
def _load_packaged_framework_content(self, content: Dict[str, Any]) -> None:
|
|
1032
674
|
"""Load framework content from packaged installation using importlib.resources."""
|
|
1033
675
|
if not files:
|
|
1034
|
-
self.logger.warning(
|
|
676
|
+
self.logger.warning(
|
|
677
|
+
"importlib.resources not available, cannot load packaged framework"
|
|
678
|
+
)
|
|
1035
679
|
self.logger.debug(f"files variable is: {files}")
|
|
1036
680
|
# Try alternative import methods
|
|
1037
681
|
try:
|
|
1038
682
|
from importlib import resources
|
|
683
|
+
|
|
1039
684
|
self.logger.info("Using importlib.resources as fallback")
|
|
1040
685
|
self._load_packaged_framework_content_fallback(content, resources)
|
|
1041
686
|
return
|
|
1042
687
|
except ImportError:
|
|
1043
|
-
self.logger.error(
|
|
688
|
+
self.logger.error(
|
|
689
|
+
"No importlib.resources available, using minimal framework"
|
|
690
|
+
)
|
|
1044
691
|
return
|
|
1045
|
-
|
|
692
|
+
|
|
1046
693
|
try:
|
|
1047
694
|
# Load INSTRUCTIONS.md
|
|
1048
695
|
instructions_content = self._load_packaged_file("INSTRUCTIONS.md")
|
|
@@ -1050,43 +697,51 @@ class FrameworkLoader:
|
|
|
1050
697
|
content["framework_instructions"] = instructions_content
|
|
1051
698
|
content["loaded"] = True
|
|
1052
699
|
# Extract and store version/timestamp metadata
|
|
1053
|
-
self._extract_metadata_from_content(
|
|
700
|
+
self._extract_metadata_from_content(
|
|
701
|
+
instructions_content, "INSTRUCTIONS.md"
|
|
702
|
+
)
|
|
1054
703
|
if self.framework_version:
|
|
1055
704
|
content["instructions_version"] = self.framework_version
|
|
1056
705
|
content["version"] = self.framework_version
|
|
1057
706
|
if self.framework_last_modified:
|
|
1058
707
|
content["instructions_last_modified"] = self.framework_last_modified
|
|
1059
|
-
|
|
708
|
+
|
|
1060
709
|
# Load BASE_PM.md
|
|
1061
710
|
base_pm_content = self._load_packaged_file("BASE_PM.md")
|
|
1062
711
|
if base_pm_content:
|
|
1063
712
|
content["base_pm_instructions"] = base_pm_content
|
|
1064
|
-
|
|
713
|
+
|
|
1065
714
|
# Load WORKFLOW.md
|
|
1066
715
|
workflow_content = self._load_packaged_file("WORKFLOW.md")
|
|
1067
716
|
if workflow_content:
|
|
1068
717
|
content["workflow_instructions"] = workflow_content
|
|
1069
718
|
content["project_workflow"] = "system"
|
|
1070
|
-
|
|
719
|
+
|
|
1071
720
|
# Load MEMORY.md
|
|
1072
721
|
memory_content = self._load_packaged_file("MEMORY.md")
|
|
1073
722
|
if memory_content:
|
|
1074
723
|
content["memory_instructions"] = memory_content
|
|
1075
724
|
content["project_memory"] = "system"
|
|
1076
|
-
|
|
725
|
+
|
|
1077
726
|
except Exception as e:
|
|
1078
727
|
self.logger.error(f"Failed to load packaged framework content: {e}")
|
|
1079
728
|
|
|
1080
|
-
def _load_packaged_framework_content_fallback(
|
|
729
|
+
def _load_packaged_framework_content_fallback(
|
|
730
|
+
self, content: Dict[str, Any], resources
|
|
731
|
+
) -> None:
|
|
1081
732
|
"""Load framework content using importlib.resources fallback."""
|
|
1082
733
|
try:
|
|
1083
734
|
# Load INSTRUCTIONS.md
|
|
1084
|
-
instructions_content = self._load_packaged_file_fallback(
|
|
735
|
+
instructions_content = self._load_packaged_file_fallback(
|
|
736
|
+
"INSTRUCTIONS.md", resources
|
|
737
|
+
)
|
|
1085
738
|
if instructions_content:
|
|
1086
739
|
content["framework_instructions"] = instructions_content
|
|
1087
740
|
content["loaded"] = True
|
|
1088
741
|
# Extract and store version/timestamp metadata
|
|
1089
|
-
self._extract_metadata_from_content(
|
|
742
|
+
self._extract_metadata_from_content(
|
|
743
|
+
instructions_content, "INSTRUCTIONS.md"
|
|
744
|
+
)
|
|
1090
745
|
if self.framework_version:
|
|
1091
746
|
content["instructions_version"] = self.framework_version
|
|
1092
747
|
content["version"] = self.framework_version
|
|
@@ -1099,7 +754,9 @@ class FrameworkLoader:
|
|
|
1099
754
|
content["base_pm_instructions"] = base_pm_content
|
|
1100
755
|
|
|
1101
756
|
# Load WORKFLOW.md
|
|
1102
|
-
workflow_content = self._load_packaged_file_fallback(
|
|
757
|
+
workflow_content = self._load_packaged_file_fallback(
|
|
758
|
+
"WORKFLOW.md", resources
|
|
759
|
+
)
|
|
1103
760
|
if workflow_content:
|
|
1104
761
|
content["workflow_instructions"] = workflow_content
|
|
1105
762
|
content["project_workflow"] = "system"
|
|
@@ -1111,7 +768,9 @@ class FrameworkLoader:
|
|
|
1111
768
|
content["project_memory"] = "system"
|
|
1112
769
|
|
|
1113
770
|
except Exception as e:
|
|
1114
|
-
self.logger.error(
|
|
771
|
+
self.logger.error(
|
|
772
|
+
f"Failed to load packaged framework content with fallback: {e}"
|
|
773
|
+
)
|
|
1115
774
|
|
|
1116
775
|
def _load_packaged_file_fallback(self, filename: str, resources) -> Optional[str]:
|
|
1117
776
|
"""Load a file from the packaged installation using importlib.resources fallback."""
|
|
@@ -1119,52 +778,52 @@ class FrameworkLoader:
|
|
|
1119
778
|
# Try different resource loading methods
|
|
1120
779
|
try:
|
|
1121
780
|
# Method 1: resources.read_text (Python 3.9+)
|
|
1122
|
-
content = resources.read_text(
|
|
781
|
+
content = resources.read_text("claude_mpm.agents", filename)
|
|
1123
782
|
self.logger.info(f"Loaded {filename} from package using read_text")
|
|
1124
783
|
return content
|
|
1125
784
|
except AttributeError:
|
|
1126
785
|
# Method 2: resources.files (Python 3.9+)
|
|
1127
|
-
agents_files = resources.files(
|
|
786
|
+
agents_files = resources.files("claude_mpm.agents")
|
|
1128
787
|
file_path = agents_files / filename
|
|
1129
788
|
if file_path.is_file():
|
|
1130
789
|
content = file_path.read_text()
|
|
1131
790
|
self.logger.info(f"Loaded {filename} from package using files")
|
|
1132
791
|
return content
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
return None
|
|
792
|
+
self.logger.warning(f"File {filename} not found in package")
|
|
793
|
+
return None
|
|
1136
794
|
except Exception as e:
|
|
1137
|
-
self.logger.error(
|
|
795
|
+
self.logger.error(
|
|
796
|
+
f"Failed to load {filename} from package with fallback: {e}"
|
|
797
|
+
)
|
|
1138
798
|
return None
|
|
1139
799
|
|
|
1140
800
|
def _load_packaged_file(self, filename: str) -> Optional[str]:
|
|
1141
801
|
"""Load a file from the packaged installation."""
|
|
1142
802
|
try:
|
|
1143
803
|
# Use importlib.resources to load file from package
|
|
1144
|
-
agents_package = files(
|
|
804
|
+
agents_package = files("claude_mpm.agents")
|
|
1145
805
|
file_path = agents_package / filename
|
|
1146
|
-
|
|
806
|
+
|
|
1147
807
|
if file_path.is_file():
|
|
1148
808
|
content = file_path.read_text()
|
|
1149
809
|
self.logger.info(f"Loaded {filename} from package")
|
|
1150
810
|
return content
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
return None
|
|
811
|
+
self.logger.warning(f"File {filename} not found in package")
|
|
812
|
+
return None
|
|
1154
813
|
except Exception as e:
|
|
1155
814
|
self.logger.error(f"Failed to load {filename} from package: {e}")
|
|
1156
815
|
return None
|
|
1157
|
-
|
|
816
|
+
|
|
1158
817
|
def _extract_metadata_from_content(self, content: str, filename: str) -> None:
|
|
1159
818
|
"""Extract metadata from content string."""
|
|
1160
819
|
import re
|
|
1161
|
-
|
|
820
|
+
|
|
1162
821
|
# Extract version
|
|
1163
822
|
version_match = re.search(r"<!-- FRAMEWORK_VERSION: (\d+) -->", content)
|
|
1164
823
|
if version_match and "INSTRUCTIONS.md" in filename:
|
|
1165
824
|
self.framework_version = version_match.group(1)
|
|
1166
825
|
self.logger.info(f"Framework version: {self.framework_version}")
|
|
1167
|
-
|
|
826
|
+
|
|
1168
827
|
# Extract timestamp
|
|
1169
828
|
timestamp_match = re.search(r"<!-- LAST_MODIFIED: ([^>]+) -->", content)
|
|
1170
829
|
if timestamp_match and "INSTRUCTIONS.md" in filename:
|
|
@@ -1178,12 +837,58 @@ class FrameworkLoader:
|
|
|
1178
837
|
Returns:
|
|
1179
838
|
Complete framework instructions ready for injection
|
|
1180
839
|
"""
|
|
840
|
+
# Import LogManager for prompt logging
|
|
841
|
+
try:
|
|
842
|
+
from .log_manager import get_log_manager
|
|
843
|
+
|
|
844
|
+
log_manager = get_log_manager()
|
|
845
|
+
except ImportError:
|
|
846
|
+
log_manager = None
|
|
847
|
+
|
|
848
|
+
# Generate the instructions
|
|
1181
849
|
if self.framework_content["loaded"]:
|
|
1182
850
|
# Build framework from components
|
|
1183
|
-
|
|
851
|
+
instructions = self._format_full_framework()
|
|
1184
852
|
else:
|
|
1185
853
|
# Use minimal fallback
|
|
1186
|
-
|
|
854
|
+
instructions = self._format_minimal_framework()
|
|
855
|
+
|
|
856
|
+
# Log the system prompt if LogManager is available
|
|
857
|
+
if log_manager:
|
|
858
|
+
try:
|
|
859
|
+
import asyncio
|
|
860
|
+
import os
|
|
861
|
+
|
|
862
|
+
# Get or create event loop
|
|
863
|
+
try:
|
|
864
|
+
loop = asyncio.get_running_loop()
|
|
865
|
+
except RuntimeError:
|
|
866
|
+
loop = asyncio.new_event_loop()
|
|
867
|
+
asyncio.set_event_loop(loop)
|
|
868
|
+
|
|
869
|
+
# Prepare metadata
|
|
870
|
+
metadata = {
|
|
871
|
+
"framework_version": self.framework_version,
|
|
872
|
+
"framework_loaded": self.framework_content.get("loaded", False),
|
|
873
|
+
"session_id": os.environ.get("CLAUDE_SESSION_ID", "unknown"),
|
|
874
|
+
"instructions_length": len(instructions),
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
# Log the prompt asynchronously
|
|
878
|
+
if loop.is_running():
|
|
879
|
+
asyncio.create_task(
|
|
880
|
+
log_manager.log_prompt("system_prompt", instructions, metadata)
|
|
881
|
+
)
|
|
882
|
+
else:
|
|
883
|
+
loop.run_until_complete(
|
|
884
|
+
log_manager.log_prompt("system_prompt", instructions, metadata)
|
|
885
|
+
)
|
|
886
|
+
|
|
887
|
+
self.logger.debug("System prompt logged to prompts directory")
|
|
888
|
+
except Exception as e:
|
|
889
|
+
self.logger.debug(f"Could not log system prompt: {e}")
|
|
890
|
+
|
|
891
|
+
return instructions
|
|
1187
892
|
|
|
1188
893
|
def _strip_metadata_comments(self, content: str) -> str:
|
|
1189
894
|
"""Strip metadata HTML comments from content.
|
|
@@ -1201,12 +906,10 @@ class FrameworkLoader:
|
|
|
1201
906
|
content,
|
|
1202
907
|
)
|
|
1203
908
|
# Also remove any leading blank lines that might result
|
|
1204
|
-
|
|
1205
|
-
return cleaned
|
|
909
|
+
return cleaned.lstrip("\n")
|
|
1206
910
|
|
|
1207
911
|
def _format_full_framework(self) -> str:
|
|
1208
912
|
"""Format full framework instructions."""
|
|
1209
|
-
from datetime import datetime
|
|
1210
913
|
|
|
1211
914
|
# Initialize output style manager on first use (ensures content is loaded)
|
|
1212
915
|
if self.output_style_manager is None:
|
|
@@ -1217,7 +920,9 @@ class FrameworkLoader:
|
|
|
1217
920
|
if self.output_style_manager:
|
|
1218
921
|
inject_output_style = self.output_style_manager.should_inject_content()
|
|
1219
922
|
if inject_output_style:
|
|
1220
|
-
self.logger.info(
|
|
923
|
+
self.logger.info(
|
|
924
|
+
"Injecting output style content into instructions for Claude < 1.0.83"
|
|
925
|
+
)
|
|
1221
926
|
|
|
1222
927
|
# If we have the full framework INSTRUCTIONS.md, use it
|
|
1223
928
|
if self.framework_content.get("framework_instructions"):
|
|
@@ -1227,10 +932,12 @@ class FrameworkLoader:
|
|
|
1227
932
|
|
|
1228
933
|
# Note: We don't add working directory CLAUDE.md here since Claude Code
|
|
1229
934
|
# already picks it up automatically. This prevents duplication.
|
|
1230
|
-
|
|
935
|
+
|
|
1231
936
|
# Add custom INSTRUCTIONS.md if present (overrides or extends framework instructions)
|
|
1232
937
|
if self.framework_content.get("custom_instructions"):
|
|
1233
|
-
level = self.framework_content.get(
|
|
938
|
+
level = self.framework_content.get(
|
|
939
|
+
"custom_instructions_level", "unknown"
|
|
940
|
+
)
|
|
1234
941
|
instructions += f"\n\n## Custom PM Instructions ({level} level)\n\n"
|
|
1235
942
|
instructions += "**The following custom instructions override or extend the framework defaults:**\n\n"
|
|
1236
943
|
instructions += self._strip_metadata_comments(
|
|
@@ -1243,7 +950,9 @@ class FrameworkLoader:
|
|
|
1243
950
|
workflow_content = self._strip_metadata_comments(
|
|
1244
951
|
self.framework_content["workflow_instructions"]
|
|
1245
952
|
)
|
|
1246
|
-
level = self.framework_content.get(
|
|
953
|
+
level = self.framework_content.get(
|
|
954
|
+
"workflow_instructions_level", "system"
|
|
955
|
+
)
|
|
1247
956
|
if level != "system":
|
|
1248
957
|
instructions += f"\n\n## Workflow Instructions ({level} level)\n\n"
|
|
1249
958
|
instructions += "**The following workflow instructions override system defaults:**\n\n"
|
|
@@ -1254,26 +963,28 @@ class FrameworkLoader:
|
|
|
1254
963
|
memory_content = self._strip_metadata_comments(
|
|
1255
964
|
self.framework_content["memory_instructions"]
|
|
1256
965
|
)
|
|
1257
|
-
level = self.framework_content.get(
|
|
966
|
+
level = self.framework_content.get(
|
|
967
|
+
"memory_instructions_level", "system"
|
|
968
|
+
)
|
|
1258
969
|
if level != "system":
|
|
1259
970
|
instructions += f"\n\n## Memory Instructions ({level} level)\n\n"
|
|
1260
971
|
instructions += "**The following memory instructions override system defaults:**\n\n"
|
|
1261
972
|
instructions += f"{memory_content}\n"
|
|
1262
|
-
|
|
973
|
+
|
|
1263
974
|
# Add actual PM memories after memory instructions
|
|
1264
975
|
if self.framework_content.get("actual_memories"):
|
|
1265
976
|
instructions += "\n\n## Current PM Memories\n\n"
|
|
1266
977
|
instructions += "**The following are your accumulated memories and knowledge from this project:**\n\n"
|
|
1267
978
|
instructions += self.framework_content["actual_memories"]
|
|
1268
979
|
instructions += "\n"
|
|
1269
|
-
|
|
980
|
+
|
|
1270
981
|
# Add agent memories if available
|
|
1271
982
|
if self.framework_content.get("agent_memories"):
|
|
1272
983
|
agent_memories = self.framework_content["agent_memories"]
|
|
1273
984
|
if agent_memories:
|
|
1274
985
|
instructions += "\n\n## Agent Memories\n\n"
|
|
1275
986
|
instructions += "**The following are accumulated memories from specialized agents:**\n\n"
|
|
1276
|
-
|
|
987
|
+
|
|
1277
988
|
for agent_name in sorted(agent_memories.keys()):
|
|
1278
989
|
memory_content = agent_memories[agent_name]
|
|
1279
990
|
if memory_content:
|
|
@@ -1296,10 +1007,12 @@ class FrameworkLoader:
|
|
|
1296
1007
|
self.framework_content["base_pm_instructions"]
|
|
1297
1008
|
)
|
|
1298
1009
|
instructions += f"\n\n{base_pm}"
|
|
1299
|
-
|
|
1010
|
+
|
|
1300
1011
|
# Inject output style content if needed (for Claude < 1.0.83)
|
|
1301
1012
|
if inject_output_style and self.output_style_manager:
|
|
1302
|
-
output_style_content = self.output_style_manager.get_injectable_content(
|
|
1013
|
+
output_style_content = self.output_style_manager.get_injectable_content(
|
|
1014
|
+
framework_loader=self
|
|
1015
|
+
)
|
|
1303
1016
|
if output_style_content:
|
|
1304
1017
|
instructions += "\n\n## Output Style Configuration\n"
|
|
1305
1018
|
instructions += "**Note: The following output style is injected for Claude < 1.0.83**\n\n"
|
|
@@ -1307,9 +1020,7 @@ class FrameworkLoader:
|
|
|
1307
1020
|
instructions += "\n"
|
|
1308
1021
|
|
|
1309
1022
|
# Clean up any trailing whitespace
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
return instructions
|
|
1023
|
+
return instructions.rstrip() + "\n"
|
|
1313
1024
|
|
|
1314
1025
|
# Otherwise fall back to generating framework
|
|
1315
1026
|
instructions = """# Claude MPM Framework Instructions
|
|
@@ -1432,23 +1143,21 @@ Extract tickets from these patterns:
|
|
|
1432
1143
|
def _generate_agent_capabilities_section(self) -> str:
|
|
1433
1144
|
"""Generate dynamic agent capabilities section from deployed agents.
|
|
1434
1145
|
Uses caching to avoid repeated file I/O and parsing operations."""
|
|
1435
|
-
|
|
1436
|
-
#
|
|
1146
|
+
|
|
1147
|
+
# Try to get from cache first
|
|
1148
|
+
cached_capabilities = self._cache_manager.get_capabilities()
|
|
1149
|
+
if cached_capabilities is not None:
|
|
1150
|
+
return cached_capabilities
|
|
1151
|
+
|
|
1152
|
+
# Will be used for updating cache later
|
|
1437
1153
|
current_time = time.time()
|
|
1438
|
-
|
|
1439
|
-
current_time - self._agent_capabilities_cache_time < self.CAPABILITIES_CACHE_TTL):
|
|
1440
|
-
cache_age = current_time - self._agent_capabilities_cache_time
|
|
1441
|
-
self.logger.debug(f"Using cached agent capabilities (age: {cache_age:.1f}s)")
|
|
1442
|
-
return self._agent_capabilities_cache
|
|
1443
|
-
|
|
1154
|
+
|
|
1444
1155
|
# Cache miss or expired - generate capabilities
|
|
1445
1156
|
self.logger.debug("Generating agent capabilities (cache miss or expired)")
|
|
1446
|
-
|
|
1157
|
+
|
|
1447
1158
|
try:
|
|
1448
1159
|
from pathlib import Path
|
|
1449
1160
|
|
|
1450
|
-
import yaml
|
|
1451
|
-
|
|
1452
1161
|
# Read directly from deployed agents in .claude/agents/
|
|
1453
1162
|
# Check multiple locations for deployed agents
|
|
1454
1163
|
# Priority order: project > user home > fallback
|
|
@@ -1456,47 +1165,55 @@ Extract tickets from these patterns:
|
|
|
1456
1165
|
Path.cwd() / ".claude" / "agents", # Project-specific agents
|
|
1457
1166
|
Path.home() / ".claude" / "agents", # User's system agents
|
|
1458
1167
|
]
|
|
1459
|
-
|
|
1168
|
+
|
|
1460
1169
|
# Collect agents from all directories with proper precedence
|
|
1461
1170
|
# Project agents override user agents with the same name
|
|
1462
1171
|
all_agents = {} # key: agent_id, value: (agent_data, priority)
|
|
1463
|
-
|
|
1172
|
+
|
|
1464
1173
|
for priority, potential_dir in enumerate(agents_dirs):
|
|
1465
1174
|
if potential_dir.exists() and any(potential_dir.glob("*.md")):
|
|
1466
1175
|
self.logger.debug(f"Found agents directory at: {potential_dir}")
|
|
1467
|
-
|
|
1176
|
+
|
|
1468
1177
|
# Collect agents from this directory
|
|
1469
1178
|
for agent_file in potential_dir.glob("*.md"):
|
|
1470
1179
|
if agent_file.name.startswith("."):
|
|
1471
1180
|
continue
|
|
1472
|
-
|
|
1181
|
+
|
|
1473
1182
|
# Parse agent metadata (with caching)
|
|
1474
1183
|
agent_data = self._parse_agent_metadata(agent_file)
|
|
1475
1184
|
if agent_data:
|
|
1476
1185
|
agent_id = agent_data["id"]
|
|
1477
1186
|
# Only add if not already present (project has priority 0, user has priority 1)
|
|
1478
1187
|
# Lower priority number wins (project > user)
|
|
1479
|
-
if
|
|
1188
|
+
if (
|
|
1189
|
+
agent_id not in all_agents
|
|
1190
|
+
or priority < all_agents[agent_id][1]
|
|
1191
|
+
):
|
|
1480
1192
|
all_agents[agent_id] = (agent_data, priority)
|
|
1481
|
-
self.logger.debug(
|
|
1193
|
+
self.logger.debug(
|
|
1194
|
+
f"Added/Updated agent {agent_id} from {potential_dir} (priority {priority})"
|
|
1195
|
+
)
|
|
1482
1196
|
|
|
1483
1197
|
if not all_agents:
|
|
1484
1198
|
self.logger.warning(f"No agents found in any location: {agents_dirs}")
|
|
1485
1199
|
result = self._get_fallback_capabilities()
|
|
1486
1200
|
# Cache the fallback result too
|
|
1487
|
-
self.
|
|
1488
|
-
self._agent_capabilities_cache_time = current_time
|
|
1201
|
+
self._cache_manager.set_capabilities(result)
|
|
1489
1202
|
return result
|
|
1490
|
-
|
|
1203
|
+
|
|
1491
1204
|
# Log agent collection summary
|
|
1492
1205
|
project_agents = [aid for aid, (_, pri) in all_agents.items() if pri == 0]
|
|
1493
1206
|
user_agents = [aid for aid, (_, pri) in all_agents.items() if pri == 1]
|
|
1494
|
-
|
|
1207
|
+
|
|
1495
1208
|
if project_agents:
|
|
1496
|
-
self.logger.info(
|
|
1209
|
+
self.logger.info(
|
|
1210
|
+
f"Loaded {len(project_agents)} project agents: {', '.join(sorted(project_agents))}"
|
|
1211
|
+
)
|
|
1497
1212
|
if user_agents:
|
|
1498
|
-
self.logger.info(
|
|
1499
|
-
|
|
1213
|
+
self.logger.info(
|
|
1214
|
+
f"Loaded {len(user_agents)} user agents: {', '.join(sorted(user_agents))}"
|
|
1215
|
+
)
|
|
1216
|
+
|
|
1500
1217
|
# Build capabilities section
|
|
1501
1218
|
section = "\n\n## Available Agent Capabilities\n\n"
|
|
1502
1219
|
|
|
@@ -1506,8 +1223,7 @@ Extract tickets from these patterns:
|
|
|
1506
1223
|
if not deployed_agents:
|
|
1507
1224
|
result = self._get_fallback_capabilities()
|
|
1508
1225
|
# Cache the fallback result
|
|
1509
|
-
self.
|
|
1510
|
-
self._agent_capabilities_cache_time = current_time
|
|
1226
|
+
self._cache_manager.set_capabilities(result)
|
|
1511
1227
|
return result
|
|
1512
1228
|
|
|
1513
1229
|
# Sort agents alphabetically by ID
|
|
@@ -1528,6 +1244,33 @@ Extract tickets from these patterns:
|
|
|
1528
1244
|
section += f"\n### {display_name} (`{agent['id']}`)\n"
|
|
1529
1245
|
section += f"{agent['description']}\n"
|
|
1530
1246
|
|
|
1247
|
+
# Add routing information if available
|
|
1248
|
+
if agent.get("routing"):
|
|
1249
|
+
routing = agent["routing"]
|
|
1250
|
+
|
|
1251
|
+
# Format routing hints for PM usage
|
|
1252
|
+
routing_hints = []
|
|
1253
|
+
|
|
1254
|
+
if routing.get("keywords"):
|
|
1255
|
+
# Show first 5 keywords for brevity
|
|
1256
|
+
keywords = routing["keywords"][:5]
|
|
1257
|
+
routing_hints.append(f"Keywords: {', '.join(keywords)}")
|
|
1258
|
+
|
|
1259
|
+
if routing.get("paths"):
|
|
1260
|
+
# Show first 3 paths for brevity
|
|
1261
|
+
paths = routing["paths"][:3]
|
|
1262
|
+
routing_hints.append(f"Paths: {', '.join(paths)}")
|
|
1263
|
+
|
|
1264
|
+
if routing.get("priority"):
|
|
1265
|
+
routing_hints.append(f"Priority: {routing['priority']}")
|
|
1266
|
+
|
|
1267
|
+
if routing_hints:
|
|
1268
|
+
section += f"- **Routing**: {' | '.join(routing_hints)}\n"
|
|
1269
|
+
|
|
1270
|
+
# Add when_to_use if present
|
|
1271
|
+
if routing.get("when_to_use"):
|
|
1272
|
+
section += f"- **When to use**: {routing['when_to_use']}\n"
|
|
1273
|
+
|
|
1531
1274
|
# Add any additional metadata if present
|
|
1532
1275
|
if agent.get("authority"):
|
|
1533
1276
|
section += f"- **Authority**: {agent['authority']}\n"
|
|
@@ -1556,10 +1299,11 @@ Extract tickets from these patterns:
|
|
|
1556
1299
|
section += f"\n**Total Available Agents**: {len(deployed_agents)}\n"
|
|
1557
1300
|
|
|
1558
1301
|
# Cache the generated capabilities
|
|
1559
|
-
self.
|
|
1560
|
-
self.
|
|
1561
|
-
|
|
1562
|
-
|
|
1302
|
+
self._cache_manager.set_capabilities(section)
|
|
1303
|
+
self.logger.debug(
|
|
1304
|
+
f"Cached agent capabilities section ({len(section)} chars)"
|
|
1305
|
+
)
|
|
1306
|
+
|
|
1563
1307
|
return section
|
|
1564
1308
|
|
|
1565
1309
|
except Exception as e:
|
|
@@ -1582,22 +1326,24 @@ Extract tickets from these patterns:
|
|
|
1582
1326
|
cache_key = str(agent_file)
|
|
1583
1327
|
file_mtime = agent_file.stat().st_mtime
|
|
1584
1328
|
current_time = time.time()
|
|
1585
|
-
|
|
1586
|
-
#
|
|
1587
|
-
|
|
1588
|
-
|
|
1329
|
+
|
|
1330
|
+
# Try to get from cache first
|
|
1331
|
+
cached_result = self._cache_manager.get_agent_metadata(cache_key)
|
|
1332
|
+
if cached_result is not None:
|
|
1333
|
+
cached_data, cached_mtime = cached_result
|
|
1589
1334
|
# Use cache if file hasn't been modified and cache isn't too old
|
|
1590
|
-
if
|
|
1591
|
-
current_time - cached_mtime < self.METADATA_CACHE_TTL):
|
|
1335
|
+
if cached_mtime == file_mtime:
|
|
1592
1336
|
self.logger.debug(f"Using cached metadata for {agent_file.name}")
|
|
1593
1337
|
return cached_data
|
|
1594
|
-
|
|
1338
|
+
|
|
1595
1339
|
# Cache miss or expired - parse the file
|
|
1596
|
-
self.logger.debug(
|
|
1597
|
-
|
|
1340
|
+
self.logger.debug(
|
|
1341
|
+
f"Parsing metadata for {agent_file.name} (cache miss or expired)"
|
|
1342
|
+
)
|
|
1343
|
+
|
|
1598
1344
|
import yaml
|
|
1599
1345
|
|
|
1600
|
-
with open(agent_file
|
|
1346
|
+
with open(agent_file) as f:
|
|
1601
1347
|
content = f.read()
|
|
1602
1348
|
|
|
1603
1349
|
# Default values
|
|
@@ -1632,15 +1378,89 @@ Extract tickets from these patterns:
|
|
|
1632
1378
|
# IMPORTANT: Do NOT add spaces to tools field - it breaks deployment!
|
|
1633
1379
|
# Tools must remain as comma-separated without spaces: "Read,Write,Edit"
|
|
1634
1380
|
|
|
1381
|
+
# Try to load routing metadata from JSON template if not in YAML frontmatter
|
|
1382
|
+
if "routing" not in agent_data:
|
|
1383
|
+
routing_data = self._load_routing_from_template(agent_file.stem)
|
|
1384
|
+
if routing_data:
|
|
1385
|
+
agent_data["routing"] = routing_data
|
|
1386
|
+
|
|
1635
1387
|
# Cache the parsed metadata
|
|
1636
|
-
self.
|
|
1637
|
-
|
|
1388
|
+
self._cache_manager.set_agent_metadata(cache_key, agent_data, file_mtime)
|
|
1389
|
+
|
|
1638
1390
|
return agent_data
|
|
1639
1391
|
|
|
1640
1392
|
except Exception as e:
|
|
1641
1393
|
self.logger.debug(f"Could not parse metadata from {agent_file}: {e}")
|
|
1642
1394
|
return None
|
|
1643
1395
|
|
|
1396
|
+
def _load_routing_from_template(self, agent_name: str) -> Optional[Dict[str, Any]]:
|
|
1397
|
+
"""Load routing metadata from agent JSON template.
|
|
1398
|
+
|
|
1399
|
+
Args:
|
|
1400
|
+
agent_name: Name of the agent (stem of the file)
|
|
1401
|
+
|
|
1402
|
+
Returns:
|
|
1403
|
+
Dictionary with routing metadata or None if not found
|
|
1404
|
+
"""
|
|
1405
|
+
try:
|
|
1406
|
+
import json
|
|
1407
|
+
|
|
1408
|
+
# Check if we have a framework path
|
|
1409
|
+
if not self.framework_path or self.framework_path == Path("__PACKAGED__"):
|
|
1410
|
+
# For packaged installations, try to load from package resources
|
|
1411
|
+
if files:
|
|
1412
|
+
try:
|
|
1413
|
+
templates_package = files("claude_mpm.agents.templates")
|
|
1414
|
+
template_file = templates_package / f"{agent_name}.json"
|
|
1415
|
+
|
|
1416
|
+
if template_file.is_file():
|
|
1417
|
+
template_content = template_file.read_text()
|
|
1418
|
+
template_data = json.loads(template_content)
|
|
1419
|
+
return template_data.get("routing")
|
|
1420
|
+
except Exception as e:
|
|
1421
|
+
self.logger.debug(
|
|
1422
|
+
f"Could not load routing from packaged template for {agent_name}: {e}"
|
|
1423
|
+
)
|
|
1424
|
+
return None
|
|
1425
|
+
|
|
1426
|
+
# For development mode, load from filesystem
|
|
1427
|
+
templates_dir = (
|
|
1428
|
+
self.framework_path / "src" / "claude_mpm" / "agents" / "templates"
|
|
1429
|
+
)
|
|
1430
|
+
template_file = templates_dir / f"{agent_name}.json"
|
|
1431
|
+
|
|
1432
|
+
if template_file.exists():
|
|
1433
|
+
with open(template_file) as f:
|
|
1434
|
+
template_data = json.load(f)
|
|
1435
|
+
return template_data.get("routing")
|
|
1436
|
+
|
|
1437
|
+
# Also check for variations in naming (underscore vs dash)
|
|
1438
|
+
# Handle common naming variations between deployed .md files and .json templates
|
|
1439
|
+
# Remove duplicates by using a set
|
|
1440
|
+
alternative_names = list(
|
|
1441
|
+
{
|
|
1442
|
+
agent_name.replace("-", "_"), # api-qa -> api_qa
|
|
1443
|
+
agent_name.replace("_", "-"), # api_qa -> api-qa
|
|
1444
|
+
agent_name.replace("-", ""), # api-qa -> apiqa
|
|
1445
|
+
agent_name.replace("_", ""), # api_qa -> apiqa
|
|
1446
|
+
}
|
|
1447
|
+
)
|
|
1448
|
+
|
|
1449
|
+
for alt_name in alternative_names:
|
|
1450
|
+
if alt_name != agent_name:
|
|
1451
|
+
alt_file = templates_dir / f"{alt_name}.json"
|
|
1452
|
+
if alt_file.exists():
|
|
1453
|
+
with open(alt_file) as f:
|
|
1454
|
+
template_data = json.load(f)
|
|
1455
|
+
return template_data.get("routing")
|
|
1456
|
+
|
|
1457
|
+
self.logger.debug(f"No JSON template found for agent: {agent_name}")
|
|
1458
|
+
return None
|
|
1459
|
+
|
|
1460
|
+
except Exception as e:
|
|
1461
|
+
self.logger.debug(f"Could not load routing metadata for {agent_name}: {e}")
|
|
1462
|
+
return None
|
|
1463
|
+
|
|
1644
1464
|
def _generate_agent_selection_guide(self, deployed_agents: list) -> str:
|
|
1645
1465
|
"""Generate Context-Aware Agent Selection guide from deployed agents.
|
|
1646
1466
|
|
|
@@ -1660,55 +1480,55 @@ Extract tickets from these patterns:
|
|
|
1660
1480
|
if "implementation" in desc_lower or (
|
|
1661
1481
|
"engineer" in agent_id and "data" not in agent_id
|
|
1662
1482
|
):
|
|
1663
|
-
selection_map[
|
|
1664
|
-
"
|
|
1665
|
-
|
|
1483
|
+
selection_map["Implementation tasks"] = (
|
|
1484
|
+
f"{agent['display_name']} (`{agent_id}`)"
|
|
1485
|
+
)
|
|
1666
1486
|
if "codebase analysis" in desc_lower or "research" in agent_id:
|
|
1667
|
-
selection_map[
|
|
1668
|
-
"
|
|
1669
|
-
|
|
1487
|
+
selection_map["Codebase analysis"] = (
|
|
1488
|
+
f"{agent['display_name']} (`{agent_id}`)"
|
|
1489
|
+
)
|
|
1670
1490
|
if "testing" in desc_lower or "qa" in agent_id:
|
|
1671
|
-
selection_map[
|
|
1672
|
-
"
|
|
1673
|
-
|
|
1491
|
+
selection_map["Testing/quality"] = (
|
|
1492
|
+
f"{agent['display_name']} (`{agent_id}`)"
|
|
1493
|
+
)
|
|
1674
1494
|
if "documentation" in desc_lower:
|
|
1675
|
-
selection_map[
|
|
1676
|
-
"
|
|
1677
|
-
|
|
1495
|
+
selection_map["Documentation"] = (
|
|
1496
|
+
f"{agent['display_name']} (`{agent_id}`)"
|
|
1497
|
+
)
|
|
1678
1498
|
if "security" in desc_lower or "sast" in desc_lower:
|
|
1679
|
-
selection_map[
|
|
1680
|
-
"
|
|
1681
|
-
|
|
1499
|
+
selection_map["Security operations"] = (
|
|
1500
|
+
f"{agent['display_name']} (`{agent_id}`)"
|
|
1501
|
+
)
|
|
1682
1502
|
if (
|
|
1683
1503
|
"deployment" in desc_lower
|
|
1684
1504
|
or "infrastructure" in desc_lower
|
|
1685
1505
|
or "ops" in agent_id
|
|
1686
1506
|
):
|
|
1687
|
-
selection_map[
|
|
1688
|
-
"
|
|
1689
|
-
|
|
1507
|
+
selection_map["Deployment/infrastructure"] = (
|
|
1508
|
+
f"{agent['display_name']} (`{agent_id}`)"
|
|
1509
|
+
)
|
|
1690
1510
|
if "data" in desc_lower and (
|
|
1691
1511
|
"pipeline" in desc_lower or "etl" in desc_lower
|
|
1692
1512
|
):
|
|
1693
|
-
selection_map[
|
|
1694
|
-
"
|
|
1695
|
-
|
|
1513
|
+
selection_map["Data pipeline/ETL"] = (
|
|
1514
|
+
f"{agent['display_name']} (`{agent_id}`)"
|
|
1515
|
+
)
|
|
1696
1516
|
if "git" in desc_lower or "version control" in desc_lower:
|
|
1697
|
-
selection_map[
|
|
1698
|
-
"
|
|
1699
|
-
|
|
1517
|
+
selection_map["Version control"] = (
|
|
1518
|
+
f"{agent['display_name']} (`{agent_id}`)"
|
|
1519
|
+
)
|
|
1700
1520
|
if "ticket" in desc_lower or "epic" in desc_lower:
|
|
1701
|
-
selection_map[
|
|
1702
|
-
"
|
|
1703
|
-
|
|
1521
|
+
selection_map["Ticket/issue management"] = (
|
|
1522
|
+
f"{agent['display_name']} (`{agent_id}`)"
|
|
1523
|
+
)
|
|
1704
1524
|
if "browser" in desc_lower or "e2e" in desc_lower:
|
|
1705
|
-
selection_map[
|
|
1706
|
-
"
|
|
1707
|
-
|
|
1525
|
+
selection_map["Browser/E2E testing"] = (
|
|
1526
|
+
f"{agent['display_name']} (`{agent_id}`)"
|
|
1527
|
+
)
|
|
1708
1528
|
if "frontend" in desc_lower or "ui" in desc_lower or "html" in desc_lower:
|
|
1709
|
-
selection_map[
|
|
1710
|
-
"
|
|
1711
|
-
|
|
1529
|
+
selection_map["Frontend/UI development"] = (
|
|
1530
|
+
f"{agent['display_name']} (`{agent_id}`)"
|
|
1531
|
+
)
|
|
1712
1532
|
|
|
1713
1533
|
# Always include PM questions
|
|
1714
1534
|
selection_map["PM questions"] = "Answer directly (only exception)"
|