claude-mpm 4.1.0__py3-none-any.whl → 4.1.2__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/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 +133 -58
- claude_mpm/agents/templates/web_ui.json +3 -3
- 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 +271 -268
- 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 +192 -141
- claude_mpm/cli/commands/monitor.py +117 -88
- claude_mpm/cli/commands/run.py +120 -84
- claude_mpm/cli/commands/run_config_checker.py +4 -5
- 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 +204 -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 +20 -23
- 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 +581 -280
- 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 -15
- 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 +93 -38
- claude_mpm/hooks/claude_hooks/hook_handler.py +130 -76
- claude_mpm/hooks/claude_hooks/hook_handler_eventbus.py +104 -77
- 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/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 +136 -106
- 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/config/__init__.py +1 -1
- claude_mpm/services/agents/deployment/config/deployment_config.py +7 -8
- 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/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 +280 -160
- 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/template_generator.py +4 -6
- claude_mpm/services/agents/registry/__init__.py +10 -6
- 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/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/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/diagnostics/__init__.py +1 -1
- claude_mpm/services/diagnostics/checks/__init__.py +6 -6
- claude_mpm/services/diagnostics/checks/agent_check.py +89 -80
- 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.0.dist-info → claude_mpm-4.1.2.dist-info}/METADATA +1 -1
- claude_mpm-4.1.2.dist-info/RECORD +498 -0
- claude_mpm-4.1.0.dist-info/RECORD +0 -494
- {claude_mpm-4.1.0.dist-info → claude_mpm-4.1.2.dist-info}/WHEEL +0 -0
- {claude_mpm-4.1.0.dist-info → claude_mpm-4.1.2.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.1.0.dist-info → claude_mpm-4.1.2.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.1.0.dist-info → claude_mpm-4.1.2.dist-info}/top_level.txt +0 -0
|
@@ -1,8 +1,6 @@
|
|
|
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
|
|
@@ -38,33 +36,33 @@ class FrameworkLoader:
|
|
|
38
36
|
2. Loading custom instructions from .claude-mpm/ directories
|
|
39
37
|
3. Preparing agent definitions
|
|
40
38
|
4. Formatting for injection
|
|
41
|
-
|
|
39
|
+
|
|
42
40
|
Custom Instructions Loading:
|
|
43
41
|
The framework loader supports custom instructions through .claude-mpm/ directories.
|
|
44
42
|
It NEVER reads from .claude/ directories to avoid conflicts with Claude Code.
|
|
45
|
-
|
|
43
|
+
|
|
46
44
|
File Loading Precedence (highest to lowest):
|
|
47
|
-
|
|
45
|
+
|
|
48
46
|
INSTRUCTIONS.md:
|
|
49
47
|
1. Project: ./.claude-mpm/INSTRUCTIONS.md
|
|
50
48
|
2. User: ~/.claude-mpm/INSTRUCTIONS.md
|
|
51
49
|
3. System: (built-in framework instructions)
|
|
52
|
-
|
|
50
|
+
|
|
53
51
|
WORKFLOW.md:
|
|
54
52
|
1. Project: ./.claude-mpm/WORKFLOW.md
|
|
55
53
|
2. User: ~/.claude-mpm/WORKFLOW.md
|
|
56
54
|
3. System: src/claude_mpm/agents/WORKFLOW.md
|
|
57
|
-
|
|
55
|
+
|
|
58
56
|
MEMORY.md:
|
|
59
57
|
1. Project: ./.claude-mpm/MEMORY.md
|
|
60
58
|
2. User: ~/.claude-mpm/MEMORY.md
|
|
61
59
|
3. System: src/claude_mpm/agents/MEMORY.md
|
|
62
|
-
|
|
60
|
+
|
|
63
61
|
Actual Memories:
|
|
64
62
|
- User: ~/.claude-mpm/memories/PM_memories.md
|
|
65
63
|
- Project: ./.claude-mpm/memories/PM_memories.md (overrides user)
|
|
66
64
|
- Agent memories: *_memories.md files (only loaded if agent is deployed)
|
|
67
|
-
|
|
65
|
+
|
|
68
66
|
Important Notes:
|
|
69
67
|
- Project-level files always override user-level files
|
|
70
68
|
- User-level files always override system defaults
|
|
@@ -87,31 +85,33 @@ class FrameworkLoader:
|
|
|
87
85
|
self.agents_dir = agents_dir
|
|
88
86
|
self.framework_version = None
|
|
89
87
|
self.framework_last_modified = None
|
|
90
|
-
|
|
88
|
+
|
|
91
89
|
# Performance optimization: Initialize caches
|
|
92
90
|
self._agent_capabilities_cache: Optional[str] = None
|
|
93
91
|
self._agent_capabilities_cache_time: float = 0
|
|
94
92
|
self._deployed_agents_cache: Optional[Set[str]] = None
|
|
95
93
|
self._deployed_agents_cache_time: float = 0
|
|
96
|
-
self._agent_metadata_cache: Dict[
|
|
94
|
+
self._agent_metadata_cache: Dict[
|
|
95
|
+
str, Tuple[Optional[Dict[str, Any]], float]
|
|
96
|
+
] = {}
|
|
97
97
|
self._memories_cache: Optional[Dict[str, Any]] = None
|
|
98
98
|
self._memories_cache_time: float = 0
|
|
99
|
-
|
|
99
|
+
|
|
100
100
|
# Cache TTL settings (in seconds)
|
|
101
101
|
self.CAPABILITIES_CACHE_TTL = 60 # 60 seconds for capabilities
|
|
102
102
|
self.DEPLOYED_AGENTS_CACHE_TTL = 30 # 30 seconds for deployed agents
|
|
103
103
|
self.METADATA_CACHE_TTL = 60 # 60 seconds for agent metadata
|
|
104
104
|
self.MEMORIES_CACHE_TTL = 60 # 60 seconds for memories
|
|
105
|
-
|
|
105
|
+
|
|
106
106
|
self.framework_content = self._load_framework_content()
|
|
107
107
|
|
|
108
108
|
# Initialize agent registry
|
|
109
109
|
self.agent_registry = AgentRegistryAdapter(self.framework_path)
|
|
110
|
-
|
|
110
|
+
|
|
111
111
|
# Initialize output style manager (must be after content is loaded)
|
|
112
112
|
self.output_style_manager = None
|
|
113
113
|
# Defer initialization until first use to ensure content is loaded
|
|
114
|
-
|
|
114
|
+
|
|
115
115
|
def clear_all_caches(self) -> None:
|
|
116
116
|
"""Clear all caches to force reload on next access."""
|
|
117
117
|
self.logger.info("Clearing all framework loader caches")
|
|
@@ -122,7 +122,7 @@ class FrameworkLoader:
|
|
|
122
122
|
self._agent_metadata_cache.clear()
|
|
123
123
|
self._memories_cache = None
|
|
124
124
|
self._memories_cache_time = 0
|
|
125
|
-
|
|
125
|
+
|
|
126
126
|
def clear_agent_caches(self) -> None:
|
|
127
127
|
"""Clear agent-related caches (capabilities, deployed agents, metadata)."""
|
|
128
128
|
self.logger.info("Clearing agent-related caches")
|
|
@@ -131,7 +131,7 @@ class FrameworkLoader:
|
|
|
131
131
|
self._deployed_agents_cache = None
|
|
132
132
|
self._deployed_agents_cache_time = 0
|
|
133
133
|
self._agent_metadata_cache.clear()
|
|
134
|
-
|
|
134
|
+
|
|
135
135
|
def clear_memory_caches(self) -> None:
|
|
136
136
|
"""Clear memory-related caches."""
|
|
137
137
|
self.logger.info("Clearing memory caches")
|
|
@@ -142,98 +142,132 @@ class FrameworkLoader:
|
|
|
142
142
|
"""Initialize output style management and deploy if applicable."""
|
|
143
143
|
try:
|
|
144
144
|
from claude_mpm.core.output_style_manager import OutputStyleManager
|
|
145
|
-
|
|
145
|
+
|
|
146
146
|
self.output_style_manager = OutputStyleManager()
|
|
147
|
-
|
|
147
|
+
|
|
148
148
|
# Log detailed output style status
|
|
149
149
|
self._log_output_style_status()
|
|
150
|
-
|
|
150
|
+
|
|
151
151
|
# Extract and save output style content (pass self to reuse loaded content)
|
|
152
|
-
output_style_content =
|
|
153
|
-
|
|
154
|
-
|
|
152
|
+
output_style_content = (
|
|
153
|
+
self.output_style_manager.extract_output_style_content(
|
|
154
|
+
framework_loader=self
|
|
155
|
+
)
|
|
156
|
+
)
|
|
157
|
+
self.output_style_manager.save_output_style(output_style_content)
|
|
158
|
+
|
|
155
159
|
# Deploy to Claude Code if supported
|
|
156
|
-
deployed = self.output_style_manager.deploy_output_style(
|
|
157
|
-
|
|
160
|
+
deployed = self.output_style_manager.deploy_output_style(
|
|
161
|
+
output_style_content
|
|
162
|
+
)
|
|
163
|
+
|
|
158
164
|
if deployed:
|
|
159
165
|
self.logger.info("✅ Output style deployed to Claude Code >= 1.0.83")
|
|
160
166
|
else:
|
|
161
|
-
self.logger.info(
|
|
162
|
-
|
|
167
|
+
self.logger.info(
|
|
168
|
+
"📝 Output style will be injected into instructions for older Claude versions"
|
|
169
|
+
)
|
|
170
|
+
|
|
163
171
|
except Exception as e:
|
|
164
172
|
self.logger.warning(f"❌ Failed to initialize output style manager: {e}")
|
|
165
173
|
# Continue without output style management
|
|
166
|
-
|
|
174
|
+
|
|
167
175
|
def _log_output_style_status(self) -> None:
|
|
168
176
|
"""Log comprehensive output style status information."""
|
|
169
177
|
if not self.output_style_manager:
|
|
170
178
|
return
|
|
171
|
-
|
|
179
|
+
|
|
172
180
|
# Claude version detection
|
|
173
181
|
claude_version = self.output_style_manager.claude_version
|
|
174
182
|
if claude_version:
|
|
175
183
|
self.logger.info(f"Claude Code version detected: {claude_version}")
|
|
176
|
-
|
|
184
|
+
|
|
177
185
|
# Check if version supports output styles
|
|
178
186
|
if self.output_style_manager.supports_output_styles():
|
|
179
187
|
self.logger.info("✅ Claude Code supports output styles (>= 1.0.83)")
|
|
180
|
-
|
|
188
|
+
|
|
181
189
|
# Check deployment status
|
|
182
190
|
output_style_path = self.output_style_manager.output_style_path
|
|
183
191
|
if output_style_path.exists():
|
|
184
|
-
self.logger.info(
|
|
192
|
+
self.logger.info(
|
|
193
|
+
f"📁 Output style file exists: {output_style_path}"
|
|
194
|
+
)
|
|
185
195
|
else:
|
|
186
|
-
self.logger.info(
|
|
187
|
-
|
|
196
|
+
self.logger.info(
|
|
197
|
+
f"📝 Output style will be created at: {output_style_path}"
|
|
198
|
+
)
|
|
199
|
+
|
|
188
200
|
else:
|
|
189
|
-
self.logger.info(
|
|
190
|
-
|
|
201
|
+
self.logger.info(
|
|
202
|
+
f"⚠️ Claude Code {claude_version} does not support output styles (< 1.0.83)"
|
|
203
|
+
)
|
|
204
|
+
self.logger.info(
|
|
205
|
+
"📝 Output style content will be injected into framework instructions"
|
|
206
|
+
)
|
|
191
207
|
else:
|
|
192
208
|
self.logger.info("⚠️ Claude Code not detected or version unknown")
|
|
193
|
-
self.logger.info(
|
|
209
|
+
self.logger.info(
|
|
210
|
+
"📝 Output style content will be injected into framework instructions as fallback"
|
|
211
|
+
)
|
|
194
212
|
|
|
195
213
|
def _detect_framework_path(self) -> Optional[Path]:
|
|
196
214
|
"""Auto-detect claude-mpm framework using unified path management."""
|
|
197
215
|
try:
|
|
198
216
|
# Use the unified path manager for consistent detection
|
|
199
|
-
from ..core.unified_paths import
|
|
217
|
+
from ..core.unified_paths import DeploymentContext, get_path_manager
|
|
200
218
|
|
|
201
219
|
path_manager = get_path_manager()
|
|
202
220
|
deployment_context = path_manager._deployment_context
|
|
203
221
|
|
|
204
222
|
# Check if we're in a packaged installation
|
|
205
|
-
if deployment_context in [
|
|
206
|
-
|
|
223
|
+
if deployment_context in [
|
|
224
|
+
DeploymentContext.PIP_INSTALL,
|
|
225
|
+
DeploymentContext.PIPX_INSTALL,
|
|
226
|
+
DeploymentContext.SYSTEM_PACKAGE,
|
|
227
|
+
]:
|
|
228
|
+
self.logger.info(
|
|
229
|
+
f"Running from packaged installation (context: {deployment_context})"
|
|
230
|
+
)
|
|
207
231
|
# Return a marker path to indicate packaged installation
|
|
208
232
|
return Path("__PACKAGED__")
|
|
209
|
-
|
|
233
|
+
if deployment_context == DeploymentContext.DEVELOPMENT:
|
|
210
234
|
# Development mode - use framework root
|
|
211
235
|
framework_root = path_manager.framework_root
|
|
212
236
|
if (framework_root / "src" / "claude_mpm" / "agents").exists():
|
|
213
|
-
self.logger.info(
|
|
237
|
+
self.logger.info(
|
|
238
|
+
f"Using claude-mpm development installation at: {framework_root}"
|
|
239
|
+
)
|
|
214
240
|
return framework_root
|
|
215
241
|
elif deployment_context == DeploymentContext.EDITABLE_INSTALL:
|
|
216
242
|
# Editable install - similar to development
|
|
217
243
|
framework_root = path_manager.framework_root
|
|
218
244
|
if (framework_root / "src" / "claude_mpm" / "agents").exists():
|
|
219
|
-
self.logger.info(
|
|
245
|
+
self.logger.info(
|
|
246
|
+
f"Using claude-mpm editable installation at: {framework_root}"
|
|
247
|
+
)
|
|
220
248
|
return framework_root
|
|
221
249
|
|
|
222
250
|
except Exception as e:
|
|
223
|
-
self.logger.warning(
|
|
251
|
+
self.logger.warning(
|
|
252
|
+
f"Failed to use unified path manager for framework detection: {e}"
|
|
253
|
+
)
|
|
224
254
|
# Fall back to original detection logic
|
|
225
|
-
pass
|
|
226
255
|
|
|
227
256
|
# Fallback: Original detection logic for compatibility
|
|
228
257
|
try:
|
|
229
258
|
# Check if the package is installed
|
|
230
259
|
import claude_mpm
|
|
260
|
+
|
|
231
261
|
package_file = Path(claude_mpm.__file__)
|
|
232
262
|
|
|
233
263
|
# For packaged installations, we don't need a framework path
|
|
234
264
|
# since we'll use importlib.resources to load files
|
|
235
|
-
if
|
|
236
|
-
|
|
265
|
+
if "site-packages" in str(package_file) or "dist-packages" in str(
|
|
266
|
+
package_file
|
|
267
|
+
):
|
|
268
|
+
self.logger.info(
|
|
269
|
+
f"Running from packaged installation at: {package_file.parent}"
|
|
270
|
+
)
|
|
237
271
|
# Return a marker path to indicate packaged installation
|
|
238
272
|
return Path("__PACKAGED__")
|
|
239
273
|
except ImportError:
|
|
@@ -276,7 +310,11 @@ class FrameworkLoader:
|
|
|
276
310
|
import subprocess
|
|
277
311
|
|
|
278
312
|
result = subprocess.run(
|
|
279
|
-
["npm", "root", "-g"],
|
|
313
|
+
["npm", "root", "-g"],
|
|
314
|
+
capture_output=True,
|
|
315
|
+
text=True,
|
|
316
|
+
timeout=5,
|
|
317
|
+
check=False,
|
|
280
318
|
)
|
|
281
319
|
if result.returncode == 0:
|
|
282
320
|
npm_root = Path(result.stdout.strip())
|
|
@@ -364,10 +402,10 @@ class FrameworkLoader:
|
|
|
364
402
|
def _migrate_memory_file(self, old_path: Path, new_path: Path) -> None:
|
|
365
403
|
"""
|
|
366
404
|
Migrate memory file from old naming convention to new.
|
|
367
|
-
|
|
405
|
+
|
|
368
406
|
WHY: Supports backward compatibility by automatically migrating from
|
|
369
407
|
the old {agent_id}_agent.md and {agent_id}.md formats to the new {agent_id}_memories.md format.
|
|
370
|
-
|
|
408
|
+
|
|
371
409
|
Args:
|
|
372
410
|
old_path: Path to the old file
|
|
373
411
|
new_path: Path to the new file
|
|
@@ -380,7 +418,9 @@ class FrameworkLoader:
|
|
|
380
418
|
new_path.write_text(content, encoding="utf-8")
|
|
381
419
|
# Remove old file
|
|
382
420
|
old_path.unlink()
|
|
383
|
-
self.logger.info(
|
|
421
|
+
self.logger.info(
|
|
422
|
+
f"Migrated memory file from {old_path.name} to {new_path.name}"
|
|
423
|
+
)
|
|
384
424
|
except Exception as e:
|
|
385
425
|
self.logger.error(f"Failed to migrate memory file {old_path.name}: {e}")
|
|
386
426
|
|
|
@@ -391,7 +431,7 @@ class FrameworkLoader:
|
|
|
391
431
|
Precedence (highest to lowest):
|
|
392
432
|
1. Project-specific: ./.claude-mpm/INSTRUCTIONS.md
|
|
393
433
|
2. User-specific: ~/.claude-mpm/INSTRUCTIONS.md
|
|
394
|
-
|
|
434
|
+
|
|
395
435
|
NOTE: We do NOT load CLAUDE.md files since Claude Code already picks them up automatically.
|
|
396
436
|
This prevents duplication of instructions.
|
|
397
437
|
|
|
@@ -407,9 +447,11 @@ class FrameworkLoader:
|
|
|
407
447
|
if loaded_content:
|
|
408
448
|
content["custom_instructions"] = loaded_content
|
|
409
449
|
content["custom_instructions_level"] = "project"
|
|
410
|
-
self.logger.info(
|
|
450
|
+
self.logger.info(
|
|
451
|
+
"Using project-specific PM instructions from .claude-mpm/INSTRUCTIONS.md"
|
|
452
|
+
)
|
|
411
453
|
return
|
|
412
|
-
|
|
454
|
+
|
|
413
455
|
# Check for user-specific INSTRUCTIONS.md
|
|
414
456
|
user_instructions_path = Path.home() / ".claude-mpm" / "INSTRUCTIONS.md"
|
|
415
457
|
if user_instructions_path.exists():
|
|
@@ -419,7 +461,9 @@ class FrameworkLoader:
|
|
|
419
461
|
if loaded_content:
|
|
420
462
|
content["custom_instructions"] = loaded_content
|
|
421
463
|
content["custom_instructions_level"] = "user"
|
|
422
|
-
self.logger.info(
|
|
464
|
+
self.logger.info(
|
|
465
|
+
"Using user-specific PM instructions from ~/.claude-mpm/INSTRUCTIONS.md"
|
|
466
|
+
)
|
|
423
467
|
return
|
|
424
468
|
|
|
425
469
|
def _load_workflow_instructions(self, content: Dict[str, Any]) -> None:
|
|
@@ -430,7 +474,7 @@ class FrameworkLoader:
|
|
|
430
474
|
1. Project-specific: ./.claude-mpm/WORKFLOW.md
|
|
431
475
|
2. User-specific: ~/.claude-mpm/WORKFLOW.md
|
|
432
476
|
3. System default: src/claude_mpm/agents/WORKFLOW.md or packaged
|
|
433
|
-
|
|
477
|
+
|
|
434
478
|
NOTE: We do NOT load from .claude/ directories to avoid conflicts.
|
|
435
479
|
|
|
436
480
|
Args:
|
|
@@ -445,9 +489,11 @@ class FrameworkLoader:
|
|
|
445
489
|
if loaded_content:
|
|
446
490
|
content["workflow_instructions"] = loaded_content
|
|
447
491
|
content["workflow_instructions_level"] = "project"
|
|
448
|
-
self.logger.info(
|
|
492
|
+
self.logger.info(
|
|
493
|
+
"Using project-specific workflow instructions from .claude-mpm/WORKFLOW.md"
|
|
494
|
+
)
|
|
449
495
|
return
|
|
450
|
-
|
|
496
|
+
|
|
451
497
|
# Check for user-specific WORKFLOW.md (medium priority)
|
|
452
498
|
user_workflow_path = Path.home() / ".claude-mpm" / "WORKFLOW.md"
|
|
453
499
|
if user_workflow_path.exists():
|
|
@@ -457,7 +503,9 @@ class FrameworkLoader:
|
|
|
457
503
|
if loaded_content:
|
|
458
504
|
content["workflow_instructions"] = loaded_content
|
|
459
505
|
content["workflow_instructions_level"] = "user"
|
|
460
|
-
self.logger.info(
|
|
506
|
+
self.logger.info(
|
|
507
|
+
"Using user-specific workflow instructions from ~/.claude-mpm/WORKFLOW.md"
|
|
508
|
+
)
|
|
461
509
|
return
|
|
462
510
|
|
|
463
511
|
# Fall back to system workflow (lowest priority)
|
|
@@ -482,7 +530,7 @@ class FrameworkLoader:
|
|
|
482
530
|
1. Project-specific: ./.claude-mpm/MEMORY.md
|
|
483
531
|
2. User-specific: ~/.claude-mpm/MEMORY.md
|
|
484
532
|
3. System default: src/claude_mpm/agents/MEMORY.md or packaged
|
|
485
|
-
|
|
533
|
+
|
|
486
534
|
NOTE: We do NOT load from .claude/ directories to avoid conflicts.
|
|
487
535
|
|
|
488
536
|
Args:
|
|
@@ -497,9 +545,11 @@ class FrameworkLoader:
|
|
|
497
545
|
if loaded_content:
|
|
498
546
|
content["memory_instructions"] = loaded_content
|
|
499
547
|
content["memory_instructions_level"] = "project"
|
|
500
|
-
self.logger.info(
|
|
548
|
+
self.logger.info(
|
|
549
|
+
"Using project-specific memory instructions from .claude-mpm/MEMORY.md"
|
|
550
|
+
)
|
|
501
551
|
return
|
|
502
|
-
|
|
552
|
+
|
|
503
553
|
# Check for user-specific MEMORY.md (medium priority)
|
|
504
554
|
user_memory_path = Path.home() / ".claude-mpm" / "MEMORY.md"
|
|
505
555
|
if user_memory_path.exists():
|
|
@@ -509,7 +559,9 @@ class FrameworkLoader:
|
|
|
509
559
|
if loaded_content:
|
|
510
560
|
content["memory_instructions"] = loaded_content
|
|
511
561
|
content["memory_instructions_level"] = "user"
|
|
512
|
-
self.logger.info(
|
|
562
|
+
self.logger.info(
|
|
563
|
+
"Using user-specific memory instructions from ~/.claude-mpm/MEMORY.md"
|
|
564
|
+
)
|
|
513
565
|
return
|
|
514
566
|
|
|
515
567
|
# Fall back to system memory instructions (lowest priority)
|
|
@@ -525,132 +577,161 @@ class FrameworkLoader:
|
|
|
525
577
|
content["memory_instructions"] = loaded_content
|
|
526
578
|
content["memory_instructions_level"] = "system"
|
|
527
579
|
self.logger.info("Using system memory instructions")
|
|
528
|
-
|
|
580
|
+
|
|
529
581
|
def _get_deployed_agents(self) -> set:
|
|
530
582
|
"""
|
|
531
583
|
Get a set of deployed agent names from .claude/agents/ directories.
|
|
532
584
|
Uses caching to avoid repeated filesystem scans.
|
|
533
|
-
|
|
585
|
+
|
|
534
586
|
Returns:
|
|
535
587
|
Set of agent names (file stems) that are deployed
|
|
536
588
|
"""
|
|
537
589
|
# Check if cache is valid
|
|
538
590
|
current_time = time.time()
|
|
539
|
-
if (
|
|
540
|
-
|
|
541
|
-
|
|
591
|
+
if (
|
|
592
|
+
self._deployed_agents_cache is not None
|
|
593
|
+
and current_time - self._deployed_agents_cache_time
|
|
594
|
+
< self.DEPLOYED_AGENTS_CACHE_TTL
|
|
595
|
+
):
|
|
596
|
+
self.logger.debug(
|
|
597
|
+
f"Using cached deployed agents (age: {current_time - self._deployed_agents_cache_time:.1f}s)"
|
|
598
|
+
)
|
|
542
599
|
return self._deployed_agents_cache
|
|
543
|
-
|
|
600
|
+
|
|
544
601
|
# Cache miss or expired - perform actual scan
|
|
545
602
|
self.logger.debug("Scanning for deployed agents (cache miss or expired)")
|
|
546
603
|
deployed = set()
|
|
547
|
-
|
|
604
|
+
|
|
548
605
|
# Check multiple locations for deployed agents
|
|
549
606
|
agents_dirs = [
|
|
550
607
|
Path.cwd() / ".claude" / "agents", # Project-specific agents
|
|
551
608
|
Path.home() / ".claude" / "agents", # User's system agents
|
|
552
609
|
]
|
|
553
|
-
|
|
610
|
+
|
|
554
611
|
for agents_dir in agents_dirs:
|
|
555
612
|
if agents_dir.exists():
|
|
556
613
|
for agent_file in agents_dir.glob("*.md"):
|
|
557
614
|
if not agent_file.name.startswith("."):
|
|
558
615
|
# Use stem to get agent name without extension
|
|
559
616
|
deployed.add(agent_file.stem)
|
|
560
|
-
self.logger.debug(
|
|
561
|
-
|
|
617
|
+
self.logger.debug(
|
|
618
|
+
f"Found deployed agent: {agent_file.stem} in {agents_dir}"
|
|
619
|
+
)
|
|
620
|
+
|
|
562
621
|
self.logger.debug(f"Total deployed agents found: {len(deployed)}")
|
|
563
|
-
|
|
622
|
+
|
|
564
623
|
# Update cache
|
|
565
624
|
self._deployed_agents_cache = deployed
|
|
566
625
|
self._deployed_agents_cache_time = current_time
|
|
567
|
-
|
|
626
|
+
|
|
568
627
|
return deployed
|
|
569
|
-
|
|
628
|
+
|
|
570
629
|
def _load_actual_memories(self, content: Dict[str, Any]) -> None:
|
|
571
630
|
"""
|
|
572
631
|
Load actual memories from both user and project directories.
|
|
573
632
|
Uses caching to avoid repeated file I/O operations.
|
|
574
|
-
|
|
633
|
+
|
|
575
634
|
Loading order:
|
|
576
635
|
1. User-level memories from ~/.claude-mpm/memories/ (global defaults)
|
|
577
636
|
2. Project-level memories from ./.claude-mpm/memories/ (overrides user)
|
|
578
|
-
|
|
637
|
+
|
|
579
638
|
This loads:
|
|
580
639
|
1. PM memories from PM_memories.md (always loaded)
|
|
581
640
|
2. Agent memories from <agent>_memories.md (only if agent is deployed)
|
|
582
|
-
|
|
641
|
+
|
|
583
642
|
Args:
|
|
584
643
|
content: Dictionary to update with actual memories
|
|
585
644
|
"""
|
|
586
645
|
# Check if cache is valid
|
|
587
646
|
current_time = time.time()
|
|
588
|
-
if (
|
|
589
|
-
|
|
647
|
+
if (
|
|
648
|
+
self._memories_cache is not None
|
|
649
|
+
and current_time - self._memories_cache_time < self.MEMORIES_CACHE_TTL
|
|
650
|
+
):
|
|
590
651
|
cache_age = current_time - self._memories_cache_time
|
|
591
652
|
self.logger.debug(f"Using cached memories (age: {cache_age:.1f}s)")
|
|
592
|
-
|
|
653
|
+
|
|
593
654
|
# Apply cached memories to content
|
|
594
655
|
if "actual_memories" in self._memories_cache:
|
|
595
656
|
content["actual_memories"] = self._memories_cache["actual_memories"]
|
|
596
657
|
if "agent_memories" in self._memories_cache:
|
|
597
658
|
content["agent_memories"] = self._memories_cache["agent_memories"]
|
|
598
659
|
return
|
|
599
|
-
|
|
660
|
+
|
|
600
661
|
# Cache miss or expired - perform actual loading
|
|
601
662
|
self.logger.debug("Loading memories from disk (cache miss or expired)")
|
|
602
|
-
|
|
663
|
+
|
|
603
664
|
# Define memory directories in priority order (user first, then project)
|
|
604
665
|
user_memories_dir = Path.home() / ".claude-mpm" / "memories"
|
|
605
666
|
project_memories_dir = Path.cwd() / ".claude-mpm" / "memories"
|
|
606
|
-
|
|
667
|
+
|
|
607
668
|
# Check for deployed agents
|
|
608
669
|
deployed_agents = self._get_deployed_agents()
|
|
609
|
-
|
|
670
|
+
|
|
610
671
|
# Track loading statistics
|
|
611
672
|
loaded_count = 0
|
|
612
673
|
skipped_count = 0
|
|
613
|
-
|
|
674
|
+
|
|
614
675
|
# Dictionary to store aggregated memories
|
|
615
676
|
pm_memories = []
|
|
616
677
|
agent_memories_dict = {}
|
|
617
|
-
|
|
678
|
+
|
|
618
679
|
# Load memories from user directory first
|
|
619
680
|
if user_memories_dir.exists():
|
|
620
|
-
self.logger.info(
|
|
681
|
+
self.logger.info(
|
|
682
|
+
f"Loading user-level memory files from: {user_memories_dir}"
|
|
683
|
+
)
|
|
621
684
|
loaded, skipped = self._load_memories_from_directory(
|
|
622
|
-
user_memories_dir,
|
|
685
|
+
user_memories_dir,
|
|
686
|
+
deployed_agents,
|
|
687
|
+
pm_memories,
|
|
688
|
+
agent_memories_dict,
|
|
689
|
+
"user",
|
|
623
690
|
)
|
|
624
691
|
loaded_count += loaded
|
|
625
692
|
skipped_count += skipped
|
|
626
693
|
else:
|
|
627
|
-
self.logger.debug(
|
|
628
|
-
|
|
694
|
+
self.logger.debug(
|
|
695
|
+
f"No user memories directory found at: {user_memories_dir}"
|
|
696
|
+
)
|
|
697
|
+
|
|
629
698
|
# Load memories from project directory (overrides user memories)
|
|
630
699
|
if project_memories_dir.exists():
|
|
631
|
-
self.logger.info(
|
|
700
|
+
self.logger.info(
|
|
701
|
+
f"Loading project-level memory files from: {project_memories_dir}"
|
|
702
|
+
)
|
|
632
703
|
loaded, skipped = self._load_memories_from_directory(
|
|
633
|
-
project_memories_dir,
|
|
704
|
+
project_memories_dir,
|
|
705
|
+
deployed_agents,
|
|
706
|
+
pm_memories,
|
|
707
|
+
agent_memories_dict,
|
|
708
|
+
"project",
|
|
634
709
|
)
|
|
635
710
|
loaded_count += loaded
|
|
636
711
|
skipped_count += skipped
|
|
637
712
|
else:
|
|
638
|
-
self.logger.debug(
|
|
639
|
-
|
|
713
|
+
self.logger.debug(
|
|
714
|
+
f"No project memories directory found at: {project_memories_dir}"
|
|
715
|
+
)
|
|
716
|
+
|
|
640
717
|
# Aggregate PM memories
|
|
641
718
|
if pm_memories:
|
|
642
719
|
aggregated_pm = self._aggregate_memories(pm_memories)
|
|
643
720
|
content["actual_memories"] = aggregated_pm
|
|
644
|
-
memory_size = len(aggregated_pm.encode(
|
|
645
|
-
self.logger.info(
|
|
646
|
-
|
|
721
|
+
memory_size = len(aggregated_pm.encode("utf-8"))
|
|
722
|
+
self.logger.info(
|
|
723
|
+
f"Aggregated PM memory ({memory_size:,} bytes) from {len(pm_memories)} source(s)"
|
|
724
|
+
)
|
|
725
|
+
|
|
647
726
|
# Store agent memories (already aggregated per agent)
|
|
648
727
|
if agent_memories_dict:
|
|
649
728
|
content["agent_memories"] = agent_memories_dict
|
|
650
729
|
for agent_name, memory_content in agent_memories_dict.items():
|
|
651
|
-
memory_size = len(memory_content.encode(
|
|
652
|
-
self.logger.debug(
|
|
653
|
-
|
|
730
|
+
memory_size = len(memory_content.encode("utf-8"))
|
|
731
|
+
self.logger.debug(
|
|
732
|
+
f"Aggregated {agent_name} memory: {memory_size:,} bytes"
|
|
733
|
+
)
|
|
734
|
+
|
|
654
735
|
# Update cache with loaded memories
|
|
655
736
|
self._memories_cache = {}
|
|
656
737
|
if "actual_memories" in content:
|
|
@@ -658,61 +739,65 @@ class FrameworkLoader:
|
|
|
658
739
|
if "agent_memories" in content:
|
|
659
740
|
self._memories_cache["agent_memories"] = content["agent_memories"]
|
|
660
741
|
self._memories_cache_time = current_time
|
|
661
|
-
|
|
742
|
+
|
|
662
743
|
# Log detailed summary
|
|
663
744
|
if loaded_count > 0 or skipped_count > 0:
|
|
664
745
|
# Count unique agents with memories
|
|
665
746
|
agent_count = len(agent_memories_dict) if agent_memories_dict else 0
|
|
666
747
|
pm_loaded = bool(content.get("actual_memories"))
|
|
667
|
-
|
|
748
|
+
|
|
668
749
|
summary_parts = []
|
|
669
750
|
if pm_loaded:
|
|
670
751
|
summary_parts.append("PM memory loaded")
|
|
671
752
|
if agent_count > 0:
|
|
672
753
|
summary_parts.append(f"{agent_count} agent memories loaded")
|
|
673
754
|
if skipped_count > 0:
|
|
674
|
-
summary_parts.append(
|
|
675
|
-
|
|
755
|
+
summary_parts.append(
|
|
756
|
+
f"{skipped_count} non-deployed agent memories skipped"
|
|
757
|
+
)
|
|
758
|
+
|
|
676
759
|
self.logger.info(f"Memory loading complete: {' | '.join(summary_parts)}")
|
|
677
|
-
|
|
760
|
+
|
|
678
761
|
# Log deployed agents for reference
|
|
679
762
|
if len(deployed_agents) > 0:
|
|
680
|
-
self.logger.debug(
|
|
681
|
-
|
|
763
|
+
self.logger.debug(
|
|
764
|
+
f"Deployed agents available for memory loading: {', '.join(sorted(deployed_agents))}"
|
|
765
|
+
)
|
|
766
|
+
|
|
682
767
|
def _load_memories_from_directory(
|
|
683
768
|
self,
|
|
684
769
|
memories_dir: Path,
|
|
685
770
|
deployed_agents: set,
|
|
686
771
|
pm_memories: list,
|
|
687
772
|
agent_memories_dict: dict,
|
|
688
|
-
source: str
|
|
773
|
+
source: str,
|
|
689
774
|
) -> tuple[int, int]:
|
|
690
775
|
"""
|
|
691
776
|
Load memories from a specific directory.
|
|
692
|
-
|
|
777
|
+
|
|
693
778
|
Args:
|
|
694
779
|
memories_dir: Directory to load memories from
|
|
695
780
|
deployed_agents: Set of deployed agent names
|
|
696
781
|
pm_memories: List to append PM memories to
|
|
697
782
|
agent_memories_dict: Dict to store agent memories
|
|
698
783
|
source: Source label ("user" or "project")
|
|
699
|
-
|
|
784
|
+
|
|
700
785
|
Returns:
|
|
701
786
|
Tuple of (loaded_count, skipped_count)
|
|
702
787
|
"""
|
|
703
788
|
loaded_count = 0
|
|
704
789
|
skipped_count = 0
|
|
705
|
-
|
|
790
|
+
|
|
706
791
|
# Load PM memories (always loaded)
|
|
707
792
|
# Support migration from both old formats
|
|
708
793
|
pm_memory_path = memories_dir / "PM_memories.md"
|
|
709
794
|
old_pm_path = memories_dir / "PM.md"
|
|
710
|
-
|
|
795
|
+
|
|
711
796
|
# Migrate from old PM.md if needed
|
|
712
797
|
if not pm_memory_path.exists() and old_pm_path.exists():
|
|
713
798
|
try:
|
|
714
799
|
old_pm_path.rename(pm_memory_path)
|
|
715
|
-
self.logger.info(
|
|
800
|
+
self.logger.info("Migrated PM.md to PM_memories.md")
|
|
716
801
|
except Exception as e:
|
|
717
802
|
self.logger.error(f"Failed to migrate PM.md: {e}")
|
|
718
803
|
pm_memory_path = old_pm_path # Fall back to old path
|
|
@@ -721,22 +806,29 @@ class FrameworkLoader:
|
|
|
721
806
|
pm_memory_path, f"PM memory ({source})"
|
|
722
807
|
)
|
|
723
808
|
if loaded_content:
|
|
724
|
-
pm_memories.append(
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
809
|
+
pm_memories.append(
|
|
810
|
+
{
|
|
811
|
+
"source": source,
|
|
812
|
+
"content": loaded_content,
|
|
813
|
+
"path": pm_memory_path,
|
|
814
|
+
}
|
|
815
|
+
)
|
|
816
|
+
memory_size = len(loaded_content.encode("utf-8"))
|
|
817
|
+
self.logger.info(
|
|
818
|
+
f"Loaded {source} PM memory: {pm_memory_path} ({memory_size:,} bytes)"
|
|
819
|
+
)
|
|
731
820
|
loaded_count += 1
|
|
732
|
-
|
|
821
|
+
|
|
733
822
|
# First, migrate any old format memory files to new format
|
|
734
823
|
# This handles backward compatibility for existing installations
|
|
735
824
|
for old_file in memories_dir.glob("*.md"):
|
|
736
825
|
# Skip files already in correct format and special files
|
|
737
|
-
if old_file.name.endswith("_memories.md") or old_file.name in [
|
|
826
|
+
if old_file.name.endswith("_memories.md") or old_file.name in [
|
|
827
|
+
"PM.md",
|
|
828
|
+
"README.md",
|
|
829
|
+
]:
|
|
738
830
|
continue
|
|
739
|
-
|
|
831
|
+
|
|
740
832
|
# Determine new name based on old format
|
|
741
833
|
if old_file.stem.endswith("_agent"):
|
|
742
834
|
# Old format: {agent_name}_agent.md -> {agent_name}_memories.md
|
|
@@ -750,17 +842,17 @@ class FrameworkLoader:
|
|
|
750
842
|
new_path = memories_dir / f"{agent_name}_memories.md"
|
|
751
843
|
if not new_path.exists():
|
|
752
844
|
self._migrate_memory_file(old_file, new_path)
|
|
753
|
-
|
|
845
|
+
|
|
754
846
|
# Load agent memories (only for deployed agents)
|
|
755
847
|
# Only process *_memories.md files to avoid README.md and other docs
|
|
756
848
|
for memory_file in memories_dir.glob("*_memories.md"):
|
|
757
849
|
# Skip PM_memories.md as we already handled it
|
|
758
850
|
if memory_file.name == "PM_memories.md":
|
|
759
851
|
continue
|
|
760
|
-
|
|
852
|
+
|
|
761
853
|
# Extract agent name from file (remove "_memories" suffix)
|
|
762
854
|
agent_name = memory_file.stem[:-9] # Remove "_memories" suffix
|
|
763
|
-
|
|
855
|
+
|
|
764
856
|
# Check if agent is deployed
|
|
765
857
|
if agent_name in deployed_agents:
|
|
766
858
|
loaded_content = self._try_load_file(
|
|
@@ -770,112 +862,127 @@ class FrameworkLoader:
|
|
|
770
862
|
# Store or merge agent memories
|
|
771
863
|
if agent_name not in agent_memories_dict:
|
|
772
864
|
agent_memories_dict[agent_name] = []
|
|
773
|
-
|
|
865
|
+
|
|
774
866
|
# If it's a list, append the new memory entry
|
|
775
867
|
if isinstance(agent_memories_dict[agent_name], list):
|
|
776
|
-
agent_memories_dict[agent_name].append(
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
868
|
+
agent_memories_dict[agent_name].append(
|
|
869
|
+
{
|
|
870
|
+
"source": source,
|
|
871
|
+
"content": loaded_content,
|
|
872
|
+
"path": memory_file,
|
|
873
|
+
}
|
|
874
|
+
)
|
|
875
|
+
|
|
876
|
+
memory_size = len(loaded_content.encode("utf-8"))
|
|
877
|
+
self.logger.info(
|
|
878
|
+
f"Loaded {source} memory for {agent_name}: {memory_file.name} ({memory_size:,} bytes)"
|
|
879
|
+
)
|
|
784
880
|
loaded_count += 1
|
|
785
881
|
else:
|
|
786
882
|
# Provide more detailed logging about why the memory was skipped
|
|
787
|
-
self.logger.info(
|
|
883
|
+
self.logger.info(
|
|
884
|
+
f"Skipped {source} memory: {memory_file.name} (agent '{agent_name}' not deployed)"
|
|
885
|
+
)
|
|
788
886
|
# Also log a debug message with available agents for diagnostics
|
|
789
|
-
if
|
|
887
|
+
if (
|
|
888
|
+
agent_name.replace("_", "-") in deployed_agents
|
|
889
|
+
or agent_name.replace("-", "_") in deployed_agents
|
|
890
|
+
):
|
|
790
891
|
# Detect naming mismatches
|
|
791
|
-
alt_name =
|
|
892
|
+
alt_name = (
|
|
893
|
+
agent_name.replace("_", "-")
|
|
894
|
+
if "_" in agent_name
|
|
895
|
+
else agent_name.replace("-", "_")
|
|
896
|
+
)
|
|
792
897
|
if alt_name in deployed_agents:
|
|
793
898
|
self.logger.warning(
|
|
794
899
|
f"Naming mismatch detected: Memory file uses '{agent_name}' but deployed agent is '{alt_name}'. "
|
|
795
900
|
f"Consider renaming {memory_file.name} to {alt_name}_memories.md"
|
|
796
901
|
)
|
|
797
902
|
skipped_count += 1
|
|
798
|
-
|
|
903
|
+
|
|
799
904
|
# After loading all memories for this directory, aggregate agent memories
|
|
800
905
|
for agent_name in list(agent_memories_dict.keys()):
|
|
801
|
-
if
|
|
906
|
+
if (
|
|
907
|
+
isinstance(agent_memories_dict[agent_name], list)
|
|
908
|
+
and agent_memories_dict[agent_name]
|
|
909
|
+
):
|
|
802
910
|
# Aggregate memories for this agent
|
|
803
911
|
aggregated = self._aggregate_memories(agent_memories_dict[agent_name])
|
|
804
912
|
agent_memories_dict[agent_name] = aggregated
|
|
805
|
-
|
|
913
|
+
|
|
806
914
|
return loaded_count, skipped_count
|
|
807
|
-
|
|
915
|
+
|
|
808
916
|
def _aggregate_memories(self, memory_entries: list) -> str:
|
|
809
917
|
"""
|
|
810
918
|
Aggregate multiple memory entries into a single memory string.
|
|
811
|
-
|
|
919
|
+
|
|
812
920
|
Strategy:
|
|
813
921
|
- Simplified to support list-based memories only
|
|
814
922
|
- Preserve all unique bullet-point items (lines starting with -)
|
|
815
923
|
- Remove exact duplicates
|
|
816
924
|
- Project-level memories take precedence over user-level
|
|
817
|
-
|
|
925
|
+
|
|
818
926
|
Args:
|
|
819
927
|
memory_entries: List of memory entries with source, content, and path
|
|
820
|
-
|
|
928
|
+
|
|
821
929
|
Returns:
|
|
822
930
|
Aggregated memory content as a string
|
|
823
931
|
"""
|
|
824
932
|
if not memory_entries:
|
|
825
933
|
return ""
|
|
826
|
-
|
|
934
|
+
|
|
827
935
|
# If only one entry, return it as-is
|
|
828
936
|
if len(memory_entries) == 1:
|
|
829
937
|
return memory_entries[0]["content"]
|
|
830
|
-
|
|
938
|
+
|
|
831
939
|
# Parse all memories into a simple list
|
|
832
940
|
all_items = {} # Dict to track items and their source
|
|
833
941
|
metadata_lines = []
|
|
834
942
|
agent_id = None
|
|
835
|
-
|
|
943
|
+
|
|
836
944
|
for entry in memory_entries:
|
|
837
945
|
content = entry["content"]
|
|
838
946
|
source = entry["source"]
|
|
839
|
-
|
|
840
|
-
for line in content.split(
|
|
947
|
+
|
|
948
|
+
for line in content.split("\n"):
|
|
841
949
|
# Check for header to extract agent_id
|
|
842
|
-
if line.startswith(
|
|
843
|
-
agent_id = line.replace(
|
|
950
|
+
if line.startswith("# Agent Memory:"):
|
|
951
|
+
agent_id = line.replace("# Agent Memory:", "").strip()
|
|
844
952
|
# Check for metadata lines
|
|
845
|
-
elif line.startswith(
|
|
953
|
+
elif line.startswith("<!-- ") and line.endswith(" -->"):
|
|
846
954
|
# Only keep metadata from project source or if not already present
|
|
847
955
|
if source == "project" or line not in metadata_lines:
|
|
848
956
|
metadata_lines.append(line)
|
|
849
957
|
# Check for list items
|
|
850
|
-
elif line.strip().startswith(
|
|
958
|
+
elif line.strip().startswith("-"):
|
|
851
959
|
# Normalize the item for comparison
|
|
852
960
|
item_text = line.strip()
|
|
853
|
-
normalized = item_text.lstrip(
|
|
854
|
-
|
|
961
|
+
normalized = item_text.lstrip("- ").strip().lower()
|
|
962
|
+
|
|
855
963
|
# Add item if new or if project source overrides user source
|
|
856
964
|
if normalized not in all_items or source == "project":
|
|
857
965
|
all_items[normalized] = (item_text, source)
|
|
858
|
-
|
|
966
|
+
|
|
859
967
|
# Build aggregated content as simple list
|
|
860
968
|
lines = []
|
|
861
|
-
|
|
969
|
+
|
|
862
970
|
# Add header
|
|
863
971
|
if agent_id:
|
|
864
972
|
lines.append(f"# Agent Memory: {agent_id}")
|
|
865
973
|
else:
|
|
866
974
|
lines.append("# Agent Memory")
|
|
867
|
-
|
|
975
|
+
|
|
868
976
|
# Add latest timestamp from metadata
|
|
869
|
-
from datetime import datetime
|
|
870
977
|
lines.append(f"<!-- Last Updated: {datetime.now().isoformat()}Z -->")
|
|
871
978
|
lines.append("")
|
|
872
|
-
|
|
979
|
+
|
|
873
980
|
# Add all unique items (sorted for consistency)
|
|
874
981
|
for normalized_key in sorted(all_items.keys()):
|
|
875
982
|
item_text, source = all_items[normalized_key]
|
|
876
983
|
lines.append(item_text)
|
|
877
|
-
|
|
878
|
-
return
|
|
984
|
+
|
|
985
|
+
return "\n".join(lines)
|
|
879
986
|
|
|
880
987
|
def _load_single_agent(
|
|
881
988
|
self, agent_file: Path
|
|
@@ -972,7 +1079,7 @@ class FrameworkLoader:
|
|
|
972
1079
|
|
|
973
1080
|
if not self.framework_path:
|
|
974
1081
|
return content
|
|
975
|
-
|
|
1082
|
+
|
|
976
1083
|
# Check if this is a packaged installation
|
|
977
1084
|
if self.framework_path == Path("__PACKAGED__"):
|
|
978
1085
|
# Load files using importlib.resources for packaged installations
|
|
@@ -981,7 +1088,11 @@ class FrameworkLoader:
|
|
|
981
1088
|
# Load from filesystem for development mode
|
|
982
1089
|
# Load framework's INSTRUCTIONS.md
|
|
983
1090
|
framework_instructions_path = (
|
|
984
|
-
self.framework_path
|
|
1091
|
+
self.framework_path
|
|
1092
|
+
/ "src"
|
|
1093
|
+
/ "claude_mpm"
|
|
1094
|
+
/ "agents"
|
|
1095
|
+
/ "INSTRUCTIONS.md"
|
|
985
1096
|
)
|
|
986
1097
|
if framework_instructions_path.exists():
|
|
987
1098
|
loaded_content = self._try_load_file(
|
|
@@ -993,12 +1104,14 @@ class FrameworkLoader:
|
|
|
993
1104
|
# Add framework version to content
|
|
994
1105
|
if self.framework_version:
|
|
995
1106
|
content["instructions_version"] = self.framework_version
|
|
996
|
-
content[
|
|
997
|
-
|
|
998
|
-
|
|
1107
|
+
content["version"] = (
|
|
1108
|
+
self.framework_version
|
|
1109
|
+
) # Update main version key
|
|
999
1110
|
# Add modification timestamp to content
|
|
1000
1111
|
if self.framework_last_modified:
|
|
1001
|
-
content["instructions_last_modified"] =
|
|
1112
|
+
content["instructions_last_modified"] = (
|
|
1113
|
+
self.framework_last_modified
|
|
1114
|
+
)
|
|
1002
1115
|
|
|
1003
1116
|
# Load BASE_PM.md for core framework requirements
|
|
1004
1117
|
base_pm_path = (
|
|
@@ -1016,7 +1129,7 @@ class FrameworkLoader:
|
|
|
1016
1129
|
|
|
1017
1130
|
# Load MEMORY.md - check for project-specific first, then system
|
|
1018
1131
|
self._load_memory_instructions(content)
|
|
1019
|
-
|
|
1132
|
+
|
|
1020
1133
|
# Load actual memories from .claude-mpm/memories/PM_memories.md
|
|
1021
1134
|
self._load_actual_memories(content)
|
|
1022
1135
|
|
|
@@ -1027,22 +1140,27 @@ class FrameworkLoader:
|
|
|
1027
1140
|
self._load_agents_directory(content, agents_dir, templates_dir, main_dir)
|
|
1028
1141
|
|
|
1029
1142
|
return content
|
|
1030
|
-
|
|
1143
|
+
|
|
1031
1144
|
def _load_packaged_framework_content(self, content: Dict[str, Any]) -> None:
|
|
1032
1145
|
"""Load framework content from packaged installation using importlib.resources."""
|
|
1033
1146
|
if not files:
|
|
1034
|
-
self.logger.warning(
|
|
1147
|
+
self.logger.warning(
|
|
1148
|
+
"importlib.resources not available, cannot load packaged framework"
|
|
1149
|
+
)
|
|
1035
1150
|
self.logger.debug(f"files variable is: {files}")
|
|
1036
1151
|
# Try alternative import methods
|
|
1037
1152
|
try:
|
|
1038
1153
|
from importlib import resources
|
|
1154
|
+
|
|
1039
1155
|
self.logger.info("Using importlib.resources as fallback")
|
|
1040
1156
|
self._load_packaged_framework_content_fallback(content, resources)
|
|
1041
1157
|
return
|
|
1042
1158
|
except ImportError:
|
|
1043
|
-
self.logger.error(
|
|
1159
|
+
self.logger.error(
|
|
1160
|
+
"No importlib.resources available, using minimal framework"
|
|
1161
|
+
)
|
|
1044
1162
|
return
|
|
1045
|
-
|
|
1163
|
+
|
|
1046
1164
|
try:
|
|
1047
1165
|
# Load INSTRUCTIONS.md
|
|
1048
1166
|
instructions_content = self._load_packaged_file("INSTRUCTIONS.md")
|
|
@@ -1050,43 +1168,51 @@ class FrameworkLoader:
|
|
|
1050
1168
|
content["framework_instructions"] = instructions_content
|
|
1051
1169
|
content["loaded"] = True
|
|
1052
1170
|
# Extract and store version/timestamp metadata
|
|
1053
|
-
self._extract_metadata_from_content(
|
|
1171
|
+
self._extract_metadata_from_content(
|
|
1172
|
+
instructions_content, "INSTRUCTIONS.md"
|
|
1173
|
+
)
|
|
1054
1174
|
if self.framework_version:
|
|
1055
1175
|
content["instructions_version"] = self.framework_version
|
|
1056
1176
|
content["version"] = self.framework_version
|
|
1057
1177
|
if self.framework_last_modified:
|
|
1058
1178
|
content["instructions_last_modified"] = self.framework_last_modified
|
|
1059
|
-
|
|
1179
|
+
|
|
1060
1180
|
# Load BASE_PM.md
|
|
1061
1181
|
base_pm_content = self._load_packaged_file("BASE_PM.md")
|
|
1062
1182
|
if base_pm_content:
|
|
1063
1183
|
content["base_pm_instructions"] = base_pm_content
|
|
1064
|
-
|
|
1184
|
+
|
|
1065
1185
|
# Load WORKFLOW.md
|
|
1066
1186
|
workflow_content = self._load_packaged_file("WORKFLOW.md")
|
|
1067
1187
|
if workflow_content:
|
|
1068
1188
|
content["workflow_instructions"] = workflow_content
|
|
1069
1189
|
content["project_workflow"] = "system"
|
|
1070
|
-
|
|
1190
|
+
|
|
1071
1191
|
# Load MEMORY.md
|
|
1072
1192
|
memory_content = self._load_packaged_file("MEMORY.md")
|
|
1073
1193
|
if memory_content:
|
|
1074
1194
|
content["memory_instructions"] = memory_content
|
|
1075
1195
|
content["project_memory"] = "system"
|
|
1076
|
-
|
|
1196
|
+
|
|
1077
1197
|
except Exception as e:
|
|
1078
1198
|
self.logger.error(f"Failed to load packaged framework content: {e}")
|
|
1079
1199
|
|
|
1080
|
-
def _load_packaged_framework_content_fallback(
|
|
1200
|
+
def _load_packaged_framework_content_fallback(
|
|
1201
|
+
self, content: Dict[str, Any], resources
|
|
1202
|
+
) -> None:
|
|
1081
1203
|
"""Load framework content using importlib.resources fallback."""
|
|
1082
1204
|
try:
|
|
1083
1205
|
# Load INSTRUCTIONS.md
|
|
1084
|
-
instructions_content = self._load_packaged_file_fallback(
|
|
1206
|
+
instructions_content = self._load_packaged_file_fallback(
|
|
1207
|
+
"INSTRUCTIONS.md", resources
|
|
1208
|
+
)
|
|
1085
1209
|
if instructions_content:
|
|
1086
1210
|
content["framework_instructions"] = instructions_content
|
|
1087
1211
|
content["loaded"] = True
|
|
1088
1212
|
# Extract and store version/timestamp metadata
|
|
1089
|
-
self._extract_metadata_from_content(
|
|
1213
|
+
self._extract_metadata_from_content(
|
|
1214
|
+
instructions_content, "INSTRUCTIONS.md"
|
|
1215
|
+
)
|
|
1090
1216
|
if self.framework_version:
|
|
1091
1217
|
content["instructions_version"] = self.framework_version
|
|
1092
1218
|
content["version"] = self.framework_version
|
|
@@ -1099,7 +1225,9 @@ class FrameworkLoader:
|
|
|
1099
1225
|
content["base_pm_instructions"] = base_pm_content
|
|
1100
1226
|
|
|
1101
1227
|
# Load WORKFLOW.md
|
|
1102
|
-
workflow_content = self._load_packaged_file_fallback(
|
|
1228
|
+
workflow_content = self._load_packaged_file_fallback(
|
|
1229
|
+
"WORKFLOW.md", resources
|
|
1230
|
+
)
|
|
1103
1231
|
if workflow_content:
|
|
1104
1232
|
content["workflow_instructions"] = workflow_content
|
|
1105
1233
|
content["project_workflow"] = "system"
|
|
@@ -1111,7 +1239,9 @@ class FrameworkLoader:
|
|
|
1111
1239
|
content["project_memory"] = "system"
|
|
1112
1240
|
|
|
1113
1241
|
except Exception as e:
|
|
1114
|
-
self.logger.error(
|
|
1242
|
+
self.logger.error(
|
|
1243
|
+
f"Failed to load packaged framework content with fallback: {e}"
|
|
1244
|
+
)
|
|
1115
1245
|
|
|
1116
1246
|
def _load_packaged_file_fallback(self, filename: str, resources) -> Optional[str]:
|
|
1117
1247
|
"""Load a file from the packaged installation using importlib.resources fallback."""
|
|
@@ -1119,52 +1249,52 @@ class FrameworkLoader:
|
|
|
1119
1249
|
# Try different resource loading methods
|
|
1120
1250
|
try:
|
|
1121
1251
|
# Method 1: resources.read_text (Python 3.9+)
|
|
1122
|
-
content = resources.read_text(
|
|
1252
|
+
content = resources.read_text("claude_mpm.agents", filename)
|
|
1123
1253
|
self.logger.info(f"Loaded {filename} from package using read_text")
|
|
1124
1254
|
return content
|
|
1125
1255
|
except AttributeError:
|
|
1126
1256
|
# Method 2: resources.files (Python 3.9+)
|
|
1127
|
-
agents_files = resources.files(
|
|
1257
|
+
agents_files = resources.files("claude_mpm.agents")
|
|
1128
1258
|
file_path = agents_files / filename
|
|
1129
1259
|
if file_path.is_file():
|
|
1130
1260
|
content = file_path.read_text()
|
|
1131
1261
|
self.logger.info(f"Loaded {filename} from package using files")
|
|
1132
1262
|
return content
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
return None
|
|
1263
|
+
self.logger.warning(f"File {filename} not found in package")
|
|
1264
|
+
return None
|
|
1136
1265
|
except Exception as e:
|
|
1137
|
-
self.logger.error(
|
|
1266
|
+
self.logger.error(
|
|
1267
|
+
f"Failed to load {filename} from package with fallback: {e}"
|
|
1268
|
+
)
|
|
1138
1269
|
return None
|
|
1139
1270
|
|
|
1140
1271
|
def _load_packaged_file(self, filename: str) -> Optional[str]:
|
|
1141
1272
|
"""Load a file from the packaged installation."""
|
|
1142
1273
|
try:
|
|
1143
1274
|
# Use importlib.resources to load file from package
|
|
1144
|
-
agents_package = files(
|
|
1275
|
+
agents_package = files("claude_mpm.agents")
|
|
1145
1276
|
file_path = agents_package / filename
|
|
1146
|
-
|
|
1277
|
+
|
|
1147
1278
|
if file_path.is_file():
|
|
1148
1279
|
content = file_path.read_text()
|
|
1149
1280
|
self.logger.info(f"Loaded {filename} from package")
|
|
1150
1281
|
return content
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
return None
|
|
1282
|
+
self.logger.warning(f"File {filename} not found in package")
|
|
1283
|
+
return None
|
|
1154
1284
|
except Exception as e:
|
|
1155
1285
|
self.logger.error(f"Failed to load {filename} from package: {e}")
|
|
1156
1286
|
return None
|
|
1157
|
-
|
|
1287
|
+
|
|
1158
1288
|
def _extract_metadata_from_content(self, content: str, filename: str) -> None:
|
|
1159
1289
|
"""Extract metadata from content string."""
|
|
1160
1290
|
import re
|
|
1161
|
-
|
|
1291
|
+
|
|
1162
1292
|
# Extract version
|
|
1163
1293
|
version_match = re.search(r"<!-- FRAMEWORK_VERSION: (\d+) -->", content)
|
|
1164
1294
|
if version_match and "INSTRUCTIONS.md" in filename:
|
|
1165
1295
|
self.framework_version = version_match.group(1)
|
|
1166
1296
|
self.logger.info(f"Framework version: {self.framework_version}")
|
|
1167
|
-
|
|
1297
|
+
|
|
1168
1298
|
# Extract timestamp
|
|
1169
1299
|
timestamp_match = re.search(r"<!-- LAST_MODIFIED: ([^>]+) -->", content)
|
|
1170
1300
|
if timestamp_match and "INSTRUCTIONS.md" in filename:
|
|
@@ -1178,12 +1308,58 @@ class FrameworkLoader:
|
|
|
1178
1308
|
Returns:
|
|
1179
1309
|
Complete framework instructions ready for injection
|
|
1180
1310
|
"""
|
|
1311
|
+
# Import LogManager for prompt logging
|
|
1312
|
+
try:
|
|
1313
|
+
from .log_manager import get_log_manager
|
|
1314
|
+
|
|
1315
|
+
log_manager = get_log_manager()
|
|
1316
|
+
except ImportError:
|
|
1317
|
+
log_manager = None
|
|
1318
|
+
|
|
1319
|
+
# Generate the instructions
|
|
1181
1320
|
if self.framework_content["loaded"]:
|
|
1182
1321
|
# Build framework from components
|
|
1183
|
-
|
|
1322
|
+
instructions = self._format_full_framework()
|
|
1184
1323
|
else:
|
|
1185
1324
|
# Use minimal fallback
|
|
1186
|
-
|
|
1325
|
+
instructions = self._format_minimal_framework()
|
|
1326
|
+
|
|
1327
|
+
# Log the system prompt if LogManager is available
|
|
1328
|
+
if log_manager:
|
|
1329
|
+
try:
|
|
1330
|
+
import asyncio
|
|
1331
|
+
import os
|
|
1332
|
+
|
|
1333
|
+
# Get or create event loop
|
|
1334
|
+
try:
|
|
1335
|
+
loop = asyncio.get_running_loop()
|
|
1336
|
+
except RuntimeError:
|
|
1337
|
+
loop = asyncio.new_event_loop()
|
|
1338
|
+
asyncio.set_event_loop(loop)
|
|
1339
|
+
|
|
1340
|
+
# Prepare metadata
|
|
1341
|
+
metadata = {
|
|
1342
|
+
"framework_version": self.framework_version,
|
|
1343
|
+
"framework_loaded": self.framework_content.get("loaded", False),
|
|
1344
|
+
"session_id": os.environ.get("CLAUDE_SESSION_ID", "unknown"),
|
|
1345
|
+
"instructions_length": len(instructions),
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
# Log the prompt asynchronously
|
|
1349
|
+
if loop.is_running():
|
|
1350
|
+
asyncio.create_task(
|
|
1351
|
+
log_manager.log_prompt("system_prompt", instructions, metadata)
|
|
1352
|
+
)
|
|
1353
|
+
else:
|
|
1354
|
+
loop.run_until_complete(
|
|
1355
|
+
log_manager.log_prompt("system_prompt", instructions, metadata)
|
|
1356
|
+
)
|
|
1357
|
+
|
|
1358
|
+
self.logger.debug("System prompt logged to prompts directory")
|
|
1359
|
+
except Exception as e:
|
|
1360
|
+
self.logger.debug(f"Could not log system prompt: {e}")
|
|
1361
|
+
|
|
1362
|
+
return instructions
|
|
1187
1363
|
|
|
1188
1364
|
def _strip_metadata_comments(self, content: str) -> str:
|
|
1189
1365
|
"""Strip metadata HTML comments from content.
|
|
@@ -1201,12 +1377,10 @@ class FrameworkLoader:
|
|
|
1201
1377
|
content,
|
|
1202
1378
|
)
|
|
1203
1379
|
# Also remove any leading blank lines that might result
|
|
1204
|
-
|
|
1205
|
-
return cleaned
|
|
1380
|
+
return cleaned.lstrip("\n")
|
|
1206
1381
|
|
|
1207
1382
|
def _format_full_framework(self) -> str:
|
|
1208
1383
|
"""Format full framework instructions."""
|
|
1209
|
-
from datetime import datetime
|
|
1210
1384
|
|
|
1211
1385
|
# Initialize output style manager on first use (ensures content is loaded)
|
|
1212
1386
|
if self.output_style_manager is None:
|
|
@@ -1217,7 +1391,9 @@ class FrameworkLoader:
|
|
|
1217
1391
|
if self.output_style_manager:
|
|
1218
1392
|
inject_output_style = self.output_style_manager.should_inject_content()
|
|
1219
1393
|
if inject_output_style:
|
|
1220
|
-
self.logger.info(
|
|
1394
|
+
self.logger.info(
|
|
1395
|
+
"Injecting output style content into instructions for Claude < 1.0.83"
|
|
1396
|
+
)
|
|
1221
1397
|
|
|
1222
1398
|
# If we have the full framework INSTRUCTIONS.md, use it
|
|
1223
1399
|
if self.framework_content.get("framework_instructions"):
|
|
@@ -1227,10 +1403,12 @@ class FrameworkLoader:
|
|
|
1227
1403
|
|
|
1228
1404
|
# Note: We don't add working directory CLAUDE.md here since Claude Code
|
|
1229
1405
|
# already picks it up automatically. This prevents duplication.
|
|
1230
|
-
|
|
1406
|
+
|
|
1231
1407
|
# Add custom INSTRUCTIONS.md if present (overrides or extends framework instructions)
|
|
1232
1408
|
if self.framework_content.get("custom_instructions"):
|
|
1233
|
-
level = self.framework_content.get(
|
|
1409
|
+
level = self.framework_content.get(
|
|
1410
|
+
"custom_instructions_level", "unknown"
|
|
1411
|
+
)
|
|
1234
1412
|
instructions += f"\n\n## Custom PM Instructions ({level} level)\n\n"
|
|
1235
1413
|
instructions += "**The following custom instructions override or extend the framework defaults:**\n\n"
|
|
1236
1414
|
instructions += self._strip_metadata_comments(
|
|
@@ -1243,7 +1421,9 @@ class FrameworkLoader:
|
|
|
1243
1421
|
workflow_content = self._strip_metadata_comments(
|
|
1244
1422
|
self.framework_content["workflow_instructions"]
|
|
1245
1423
|
)
|
|
1246
|
-
level = self.framework_content.get(
|
|
1424
|
+
level = self.framework_content.get(
|
|
1425
|
+
"workflow_instructions_level", "system"
|
|
1426
|
+
)
|
|
1247
1427
|
if level != "system":
|
|
1248
1428
|
instructions += f"\n\n## Workflow Instructions ({level} level)\n\n"
|
|
1249
1429
|
instructions += "**The following workflow instructions override system defaults:**\n\n"
|
|
@@ -1254,26 +1434,28 @@ class FrameworkLoader:
|
|
|
1254
1434
|
memory_content = self._strip_metadata_comments(
|
|
1255
1435
|
self.framework_content["memory_instructions"]
|
|
1256
1436
|
)
|
|
1257
|
-
level = self.framework_content.get(
|
|
1437
|
+
level = self.framework_content.get(
|
|
1438
|
+
"memory_instructions_level", "system"
|
|
1439
|
+
)
|
|
1258
1440
|
if level != "system":
|
|
1259
1441
|
instructions += f"\n\n## Memory Instructions ({level} level)\n\n"
|
|
1260
1442
|
instructions += "**The following memory instructions override system defaults:**\n\n"
|
|
1261
1443
|
instructions += f"{memory_content}\n"
|
|
1262
|
-
|
|
1444
|
+
|
|
1263
1445
|
# Add actual PM memories after memory instructions
|
|
1264
1446
|
if self.framework_content.get("actual_memories"):
|
|
1265
1447
|
instructions += "\n\n## Current PM Memories\n\n"
|
|
1266
1448
|
instructions += "**The following are your accumulated memories and knowledge from this project:**\n\n"
|
|
1267
1449
|
instructions += self.framework_content["actual_memories"]
|
|
1268
1450
|
instructions += "\n"
|
|
1269
|
-
|
|
1451
|
+
|
|
1270
1452
|
# Add agent memories if available
|
|
1271
1453
|
if self.framework_content.get("agent_memories"):
|
|
1272
1454
|
agent_memories = self.framework_content["agent_memories"]
|
|
1273
1455
|
if agent_memories:
|
|
1274
1456
|
instructions += "\n\n## Agent Memories\n\n"
|
|
1275
1457
|
instructions += "**The following are accumulated memories from specialized agents:**\n\n"
|
|
1276
|
-
|
|
1458
|
+
|
|
1277
1459
|
for agent_name in sorted(agent_memories.keys()):
|
|
1278
1460
|
memory_content = agent_memories[agent_name]
|
|
1279
1461
|
if memory_content:
|
|
@@ -1296,10 +1478,12 @@ class FrameworkLoader:
|
|
|
1296
1478
|
self.framework_content["base_pm_instructions"]
|
|
1297
1479
|
)
|
|
1298
1480
|
instructions += f"\n\n{base_pm}"
|
|
1299
|
-
|
|
1481
|
+
|
|
1300
1482
|
# Inject output style content if needed (for Claude < 1.0.83)
|
|
1301
1483
|
if inject_output_style and self.output_style_manager:
|
|
1302
|
-
output_style_content = self.output_style_manager.get_injectable_content(
|
|
1484
|
+
output_style_content = self.output_style_manager.get_injectable_content(
|
|
1485
|
+
framework_loader=self
|
|
1486
|
+
)
|
|
1303
1487
|
if output_style_content:
|
|
1304
1488
|
instructions += "\n\n## Output Style Configuration\n"
|
|
1305
1489
|
instructions += "**Note: The following output style is injected for Claude < 1.0.83**\n\n"
|
|
@@ -1307,9 +1491,7 @@ class FrameworkLoader:
|
|
|
1307
1491
|
instructions += "\n"
|
|
1308
1492
|
|
|
1309
1493
|
# Clean up any trailing whitespace
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
return instructions
|
|
1494
|
+
return instructions.rstrip() + "\n"
|
|
1313
1495
|
|
|
1314
1496
|
# Otherwise fall back to generating framework
|
|
1315
1497
|
instructions = """# Claude MPM Framework Instructions
|
|
@@ -1432,23 +1614,26 @@ Extract tickets from these patterns:
|
|
|
1432
1614
|
def _generate_agent_capabilities_section(self) -> str:
|
|
1433
1615
|
"""Generate dynamic agent capabilities section from deployed agents.
|
|
1434
1616
|
Uses caching to avoid repeated file I/O and parsing operations."""
|
|
1435
|
-
|
|
1617
|
+
|
|
1436
1618
|
# Check if cache is valid
|
|
1437
1619
|
current_time = time.time()
|
|
1438
|
-
if (
|
|
1439
|
-
|
|
1620
|
+
if (
|
|
1621
|
+
self._agent_capabilities_cache is not None
|
|
1622
|
+
and current_time - self._agent_capabilities_cache_time
|
|
1623
|
+
< self.CAPABILITIES_CACHE_TTL
|
|
1624
|
+
):
|
|
1440
1625
|
cache_age = current_time - self._agent_capabilities_cache_time
|
|
1441
|
-
self.logger.debug(
|
|
1626
|
+
self.logger.debug(
|
|
1627
|
+
f"Using cached agent capabilities (age: {cache_age:.1f}s)"
|
|
1628
|
+
)
|
|
1442
1629
|
return self._agent_capabilities_cache
|
|
1443
|
-
|
|
1630
|
+
|
|
1444
1631
|
# Cache miss or expired - generate capabilities
|
|
1445
1632
|
self.logger.debug("Generating agent capabilities (cache miss or expired)")
|
|
1446
|
-
|
|
1633
|
+
|
|
1447
1634
|
try:
|
|
1448
1635
|
from pathlib import Path
|
|
1449
1636
|
|
|
1450
|
-
import yaml
|
|
1451
|
-
|
|
1452
1637
|
# Read directly from deployed agents in .claude/agents/
|
|
1453
1638
|
# Check multiple locations for deployed agents
|
|
1454
1639
|
# Priority order: project > user home > fallback
|
|
@@ -1456,29 +1641,34 @@ Extract tickets from these patterns:
|
|
|
1456
1641
|
Path.cwd() / ".claude" / "agents", # Project-specific agents
|
|
1457
1642
|
Path.home() / ".claude" / "agents", # User's system agents
|
|
1458
1643
|
]
|
|
1459
|
-
|
|
1644
|
+
|
|
1460
1645
|
# Collect agents from all directories with proper precedence
|
|
1461
1646
|
# Project agents override user agents with the same name
|
|
1462
1647
|
all_agents = {} # key: agent_id, value: (agent_data, priority)
|
|
1463
|
-
|
|
1648
|
+
|
|
1464
1649
|
for priority, potential_dir in enumerate(agents_dirs):
|
|
1465
1650
|
if potential_dir.exists() and any(potential_dir.glob("*.md")):
|
|
1466
1651
|
self.logger.debug(f"Found agents directory at: {potential_dir}")
|
|
1467
|
-
|
|
1652
|
+
|
|
1468
1653
|
# Collect agents from this directory
|
|
1469
1654
|
for agent_file in potential_dir.glob("*.md"):
|
|
1470
1655
|
if agent_file.name.startswith("."):
|
|
1471
1656
|
continue
|
|
1472
|
-
|
|
1657
|
+
|
|
1473
1658
|
# Parse agent metadata (with caching)
|
|
1474
1659
|
agent_data = self._parse_agent_metadata(agent_file)
|
|
1475
1660
|
if agent_data:
|
|
1476
1661
|
agent_id = agent_data["id"]
|
|
1477
1662
|
# Only add if not already present (project has priority 0, user has priority 1)
|
|
1478
1663
|
# Lower priority number wins (project > user)
|
|
1479
|
-
if
|
|
1664
|
+
if (
|
|
1665
|
+
agent_id not in all_agents
|
|
1666
|
+
or priority < all_agents[agent_id][1]
|
|
1667
|
+
):
|
|
1480
1668
|
all_agents[agent_id] = (agent_data, priority)
|
|
1481
|
-
self.logger.debug(
|
|
1669
|
+
self.logger.debug(
|
|
1670
|
+
f"Added/Updated agent {agent_id} from {potential_dir} (priority {priority})"
|
|
1671
|
+
)
|
|
1482
1672
|
|
|
1483
1673
|
if not all_agents:
|
|
1484
1674
|
self.logger.warning(f"No agents found in any location: {agents_dirs}")
|
|
@@ -1487,16 +1677,20 @@ Extract tickets from these patterns:
|
|
|
1487
1677
|
self._agent_capabilities_cache = result
|
|
1488
1678
|
self._agent_capabilities_cache_time = current_time
|
|
1489
1679
|
return result
|
|
1490
|
-
|
|
1680
|
+
|
|
1491
1681
|
# Log agent collection summary
|
|
1492
1682
|
project_agents = [aid for aid, (_, pri) in all_agents.items() if pri == 0]
|
|
1493
1683
|
user_agents = [aid for aid, (_, pri) in all_agents.items() if pri == 1]
|
|
1494
|
-
|
|
1684
|
+
|
|
1495
1685
|
if project_agents:
|
|
1496
|
-
self.logger.info(
|
|
1686
|
+
self.logger.info(
|
|
1687
|
+
f"Loaded {len(project_agents)} project agents: {', '.join(sorted(project_agents))}"
|
|
1688
|
+
)
|
|
1497
1689
|
if user_agents:
|
|
1498
|
-
self.logger.info(
|
|
1499
|
-
|
|
1690
|
+
self.logger.info(
|
|
1691
|
+
f"Loaded {len(user_agents)} user agents: {', '.join(sorted(user_agents))}"
|
|
1692
|
+
)
|
|
1693
|
+
|
|
1500
1694
|
# Build capabilities section
|
|
1501
1695
|
section = "\n\n## Available Agent Capabilities\n\n"
|
|
1502
1696
|
|
|
@@ -1528,6 +1722,33 @@ Extract tickets from these patterns:
|
|
|
1528
1722
|
section += f"\n### {display_name} (`{agent['id']}`)\n"
|
|
1529
1723
|
section += f"{agent['description']}\n"
|
|
1530
1724
|
|
|
1725
|
+
# Add routing information if available
|
|
1726
|
+
if agent.get("routing"):
|
|
1727
|
+
routing = agent["routing"]
|
|
1728
|
+
|
|
1729
|
+
# Format routing hints for PM usage
|
|
1730
|
+
routing_hints = []
|
|
1731
|
+
|
|
1732
|
+
if routing.get("keywords"):
|
|
1733
|
+
# Show first 5 keywords for brevity
|
|
1734
|
+
keywords = routing["keywords"][:5]
|
|
1735
|
+
routing_hints.append(f"Keywords: {', '.join(keywords)}")
|
|
1736
|
+
|
|
1737
|
+
if routing.get("paths"):
|
|
1738
|
+
# Show first 3 paths for brevity
|
|
1739
|
+
paths = routing["paths"][:3]
|
|
1740
|
+
routing_hints.append(f"Paths: {', '.join(paths)}")
|
|
1741
|
+
|
|
1742
|
+
if routing.get("priority"):
|
|
1743
|
+
routing_hints.append(f"Priority: {routing['priority']}")
|
|
1744
|
+
|
|
1745
|
+
if routing_hints:
|
|
1746
|
+
section += f"- **Routing**: {' | '.join(routing_hints)}\n"
|
|
1747
|
+
|
|
1748
|
+
# Add when_to_use if present
|
|
1749
|
+
if routing.get("when_to_use"):
|
|
1750
|
+
section += f"- **When to use**: {routing['when_to_use']}\n"
|
|
1751
|
+
|
|
1531
1752
|
# Add any additional metadata if present
|
|
1532
1753
|
if agent.get("authority"):
|
|
1533
1754
|
section += f"- **Authority**: {agent['authority']}\n"
|
|
@@ -1558,8 +1779,10 @@ Extract tickets from these patterns:
|
|
|
1558
1779
|
# Cache the generated capabilities
|
|
1559
1780
|
self._agent_capabilities_cache = section
|
|
1560
1781
|
self._agent_capabilities_cache_time = current_time
|
|
1561
|
-
self.logger.debug(
|
|
1562
|
-
|
|
1782
|
+
self.logger.debug(
|
|
1783
|
+
f"Cached agent capabilities section ({len(section)} chars)"
|
|
1784
|
+
)
|
|
1785
|
+
|
|
1563
1786
|
return section
|
|
1564
1787
|
|
|
1565
1788
|
except Exception as e:
|
|
@@ -1582,22 +1805,26 @@ Extract tickets from these patterns:
|
|
|
1582
1805
|
cache_key = str(agent_file)
|
|
1583
1806
|
file_mtime = agent_file.stat().st_mtime
|
|
1584
1807
|
current_time = time.time()
|
|
1585
|
-
|
|
1808
|
+
|
|
1586
1809
|
# Check if we have cached data for this file
|
|
1587
1810
|
if cache_key in self._agent_metadata_cache:
|
|
1588
1811
|
cached_data, cached_mtime = self._agent_metadata_cache[cache_key]
|
|
1589
1812
|
# Use cache if file hasn't been modified and cache isn't too old
|
|
1590
|
-
if (
|
|
1591
|
-
|
|
1813
|
+
if (
|
|
1814
|
+
cached_mtime == file_mtime
|
|
1815
|
+
and current_time - cached_mtime < self.METADATA_CACHE_TTL
|
|
1816
|
+
):
|
|
1592
1817
|
self.logger.debug(f"Using cached metadata for {agent_file.name}")
|
|
1593
1818
|
return cached_data
|
|
1594
|
-
|
|
1819
|
+
|
|
1595
1820
|
# Cache miss or expired - parse the file
|
|
1596
|
-
self.logger.debug(
|
|
1597
|
-
|
|
1821
|
+
self.logger.debug(
|
|
1822
|
+
f"Parsing metadata for {agent_file.name} (cache miss or expired)"
|
|
1823
|
+
)
|
|
1824
|
+
|
|
1598
1825
|
import yaml
|
|
1599
1826
|
|
|
1600
|
-
with open(agent_file
|
|
1827
|
+
with open(agent_file) as f:
|
|
1601
1828
|
content = f.read()
|
|
1602
1829
|
|
|
1603
1830
|
# Default values
|
|
@@ -1632,15 +1859,89 @@ Extract tickets from these patterns:
|
|
|
1632
1859
|
# IMPORTANT: Do NOT add spaces to tools field - it breaks deployment!
|
|
1633
1860
|
# Tools must remain as comma-separated without spaces: "Read,Write,Edit"
|
|
1634
1861
|
|
|
1862
|
+
# Try to load routing metadata from JSON template if not in YAML frontmatter
|
|
1863
|
+
if "routing" not in agent_data:
|
|
1864
|
+
routing_data = self._load_routing_from_template(agent_file.stem)
|
|
1865
|
+
if routing_data:
|
|
1866
|
+
agent_data["routing"] = routing_data
|
|
1867
|
+
|
|
1635
1868
|
# Cache the parsed metadata
|
|
1636
1869
|
self._agent_metadata_cache[cache_key] = (agent_data, file_mtime)
|
|
1637
|
-
|
|
1870
|
+
|
|
1638
1871
|
return agent_data
|
|
1639
1872
|
|
|
1640
1873
|
except Exception as e:
|
|
1641
1874
|
self.logger.debug(f"Could not parse metadata from {agent_file}: {e}")
|
|
1642
1875
|
return None
|
|
1643
1876
|
|
|
1877
|
+
def _load_routing_from_template(self, agent_name: str) -> Optional[Dict[str, Any]]:
|
|
1878
|
+
"""Load routing metadata from agent JSON template.
|
|
1879
|
+
|
|
1880
|
+
Args:
|
|
1881
|
+
agent_name: Name of the agent (stem of the file)
|
|
1882
|
+
|
|
1883
|
+
Returns:
|
|
1884
|
+
Dictionary with routing metadata or None if not found
|
|
1885
|
+
"""
|
|
1886
|
+
try:
|
|
1887
|
+
import json
|
|
1888
|
+
|
|
1889
|
+
# Check if we have a framework path
|
|
1890
|
+
if not self.framework_path or self.framework_path == Path("__PACKAGED__"):
|
|
1891
|
+
# For packaged installations, try to load from package resources
|
|
1892
|
+
if files:
|
|
1893
|
+
try:
|
|
1894
|
+
templates_package = files("claude_mpm.agents.templates")
|
|
1895
|
+
template_file = templates_package / f"{agent_name}.json"
|
|
1896
|
+
|
|
1897
|
+
if template_file.is_file():
|
|
1898
|
+
template_content = template_file.read_text()
|
|
1899
|
+
template_data = json.loads(template_content)
|
|
1900
|
+
return template_data.get("routing")
|
|
1901
|
+
except Exception as e:
|
|
1902
|
+
self.logger.debug(
|
|
1903
|
+
f"Could not load routing from packaged template for {agent_name}: {e}"
|
|
1904
|
+
)
|
|
1905
|
+
return None
|
|
1906
|
+
|
|
1907
|
+
# For development mode, load from filesystem
|
|
1908
|
+
templates_dir = (
|
|
1909
|
+
self.framework_path / "src" / "claude_mpm" / "agents" / "templates"
|
|
1910
|
+
)
|
|
1911
|
+
template_file = templates_dir / f"{agent_name}.json"
|
|
1912
|
+
|
|
1913
|
+
if template_file.exists():
|
|
1914
|
+
with open(template_file) as f:
|
|
1915
|
+
template_data = json.load(f)
|
|
1916
|
+
return template_data.get("routing")
|
|
1917
|
+
|
|
1918
|
+
# Also check for variations in naming (underscore vs dash)
|
|
1919
|
+
# Handle common naming variations between deployed .md files and .json templates
|
|
1920
|
+
# Remove duplicates by using a set
|
|
1921
|
+
alternative_names = list(
|
|
1922
|
+
{
|
|
1923
|
+
agent_name.replace("-", "_"), # api-qa -> api_qa
|
|
1924
|
+
agent_name.replace("_", "-"), # api_qa -> api-qa
|
|
1925
|
+
agent_name.replace("-", ""), # api-qa -> apiqa
|
|
1926
|
+
agent_name.replace("_", ""), # api_qa -> apiqa
|
|
1927
|
+
}
|
|
1928
|
+
)
|
|
1929
|
+
|
|
1930
|
+
for alt_name in alternative_names:
|
|
1931
|
+
if alt_name != agent_name:
|
|
1932
|
+
alt_file = templates_dir / f"{alt_name}.json"
|
|
1933
|
+
if alt_file.exists():
|
|
1934
|
+
with open(alt_file) as f:
|
|
1935
|
+
template_data = json.load(f)
|
|
1936
|
+
return template_data.get("routing")
|
|
1937
|
+
|
|
1938
|
+
self.logger.debug(f"No JSON template found for agent: {agent_name}")
|
|
1939
|
+
return None
|
|
1940
|
+
|
|
1941
|
+
except Exception as e:
|
|
1942
|
+
self.logger.debug(f"Could not load routing metadata for {agent_name}: {e}")
|
|
1943
|
+
return None
|
|
1944
|
+
|
|
1644
1945
|
def _generate_agent_selection_guide(self, deployed_agents: list) -> str:
|
|
1645
1946
|
"""Generate Context-Aware Agent Selection guide from deployed agents.
|
|
1646
1947
|
|
|
@@ -1660,55 +1961,55 @@ Extract tickets from these patterns:
|
|
|
1660
1961
|
if "implementation" in desc_lower or (
|
|
1661
1962
|
"engineer" in agent_id and "data" not in agent_id
|
|
1662
1963
|
):
|
|
1663
|
-
selection_map[
|
|
1664
|
-
"
|
|
1665
|
-
|
|
1964
|
+
selection_map["Implementation tasks"] = (
|
|
1965
|
+
f"{agent['display_name']} (`{agent_id}`)"
|
|
1966
|
+
)
|
|
1666
1967
|
if "codebase analysis" in desc_lower or "research" in agent_id:
|
|
1667
|
-
selection_map[
|
|
1668
|
-
"
|
|
1669
|
-
|
|
1968
|
+
selection_map["Codebase analysis"] = (
|
|
1969
|
+
f"{agent['display_name']} (`{agent_id}`)"
|
|
1970
|
+
)
|
|
1670
1971
|
if "testing" in desc_lower or "qa" in agent_id:
|
|
1671
|
-
selection_map[
|
|
1672
|
-
"
|
|
1673
|
-
|
|
1972
|
+
selection_map["Testing/quality"] = (
|
|
1973
|
+
f"{agent['display_name']} (`{agent_id}`)"
|
|
1974
|
+
)
|
|
1674
1975
|
if "documentation" in desc_lower:
|
|
1675
|
-
selection_map[
|
|
1676
|
-
"
|
|
1677
|
-
|
|
1976
|
+
selection_map["Documentation"] = (
|
|
1977
|
+
f"{agent['display_name']} (`{agent_id}`)"
|
|
1978
|
+
)
|
|
1678
1979
|
if "security" in desc_lower or "sast" in desc_lower:
|
|
1679
|
-
selection_map[
|
|
1680
|
-
"
|
|
1681
|
-
|
|
1980
|
+
selection_map["Security operations"] = (
|
|
1981
|
+
f"{agent['display_name']} (`{agent_id}`)"
|
|
1982
|
+
)
|
|
1682
1983
|
if (
|
|
1683
1984
|
"deployment" in desc_lower
|
|
1684
1985
|
or "infrastructure" in desc_lower
|
|
1685
1986
|
or "ops" in agent_id
|
|
1686
1987
|
):
|
|
1687
|
-
selection_map[
|
|
1688
|
-
"
|
|
1689
|
-
|
|
1988
|
+
selection_map["Deployment/infrastructure"] = (
|
|
1989
|
+
f"{agent['display_name']} (`{agent_id}`)"
|
|
1990
|
+
)
|
|
1690
1991
|
if "data" in desc_lower and (
|
|
1691
1992
|
"pipeline" in desc_lower or "etl" in desc_lower
|
|
1692
1993
|
):
|
|
1693
|
-
selection_map[
|
|
1694
|
-
"
|
|
1695
|
-
|
|
1994
|
+
selection_map["Data pipeline/ETL"] = (
|
|
1995
|
+
f"{agent['display_name']} (`{agent_id}`)"
|
|
1996
|
+
)
|
|
1696
1997
|
if "git" in desc_lower or "version control" in desc_lower:
|
|
1697
|
-
selection_map[
|
|
1698
|
-
"
|
|
1699
|
-
|
|
1998
|
+
selection_map["Version control"] = (
|
|
1999
|
+
f"{agent['display_name']} (`{agent_id}`)"
|
|
2000
|
+
)
|
|
1700
2001
|
if "ticket" in desc_lower or "epic" in desc_lower:
|
|
1701
|
-
selection_map[
|
|
1702
|
-
"
|
|
1703
|
-
|
|
2002
|
+
selection_map["Ticket/issue management"] = (
|
|
2003
|
+
f"{agent['display_name']} (`{agent_id}`)"
|
|
2004
|
+
)
|
|
1704
2005
|
if "browser" in desc_lower or "e2e" in desc_lower:
|
|
1705
|
-
selection_map[
|
|
1706
|
-
"
|
|
1707
|
-
|
|
2006
|
+
selection_map["Browser/E2E testing"] = (
|
|
2007
|
+
f"{agent['display_name']} (`{agent_id}`)"
|
|
2008
|
+
)
|
|
1708
2009
|
if "frontend" in desc_lower or "ui" in desc_lower or "html" in desc_lower:
|
|
1709
|
-
selection_map[
|
|
1710
|
-
"
|
|
1711
|
-
|
|
2010
|
+
selection_map["Frontend/UI development"] = (
|
|
2011
|
+
f"{agent['display_name']} (`{agent_id}`)"
|
|
2012
|
+
)
|
|
1712
2013
|
|
|
1713
2014
|
# Always include PM questions
|
|
1714
2015
|
selection_map["PM questions"] = "Answer directly (only exception)"
|