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
|
@@ -0,0 +1,637 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Memory Management Service
|
|
3
|
+
=========================
|
|
4
|
+
|
|
5
|
+
WHY: Centralizes all memory-related operations that were previously scattered
|
|
6
|
+
across the framework_loader.py god class. This service handles loading,
|
|
7
|
+
aggregation, deduplication, and migration of agent memories.
|
|
8
|
+
|
|
9
|
+
DESIGN DECISION:
|
|
10
|
+
- Extracted from framework_loader.py to follow Single Responsibility Principle
|
|
11
|
+
- Uses dependency injection for ICacheManager and IPathResolver
|
|
12
|
+
- Maintains backward compatibility with legacy memory file formats
|
|
13
|
+
- Implements proper memory precedence: project > user > system
|
|
14
|
+
|
|
15
|
+
ARCHITECTURE:
|
|
16
|
+
- IMemoryManager interface implementation
|
|
17
|
+
- Loads PM memories and agent-specific memories
|
|
18
|
+
- Handles memory aggregation and deduplication
|
|
19
|
+
- Migrates legacy memory formats automatically
|
|
20
|
+
- Uses caching for performance optimization
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import logging
|
|
24
|
+
from datetime import datetime
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
from typing import Any, Dict, List, Optional, Set
|
|
27
|
+
|
|
28
|
+
from ...core.logger import get_logger
|
|
29
|
+
from .service_interfaces import ICacheManager, IMemoryManager, IPathResolver
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class MemoryManager(IMemoryManager):
|
|
33
|
+
"""
|
|
34
|
+
Memory management service for agent memories.
|
|
35
|
+
|
|
36
|
+
This service handles:
|
|
37
|
+
1. Loading PM memories from PM_memories.md files
|
|
38
|
+
2. Loading agent-specific memories (only for deployed agents)
|
|
39
|
+
3. Memory aggregation and deduplication
|
|
40
|
+
4. Legacy format migration (e.g., PM.md -> PM_memories.md)
|
|
41
|
+
5. Memory caching for performance
|
|
42
|
+
|
|
43
|
+
Memory Loading Order:
|
|
44
|
+
- User-level memories: ~/.claude-mpm/memories/ (global defaults)
|
|
45
|
+
- Project-level memories: ./.claude-mpm/memories/ (overrides user)
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
def __init__(
|
|
49
|
+
self,
|
|
50
|
+
cache_manager: ICacheManager,
|
|
51
|
+
path_resolver: IPathResolver,
|
|
52
|
+
logger: Optional[logging.Logger] = None,
|
|
53
|
+
):
|
|
54
|
+
"""
|
|
55
|
+
Initialize memory manager.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
cache_manager: Cache manager for memory caching
|
|
59
|
+
path_resolver: Path resolver for finding memory directories
|
|
60
|
+
logger: Optional logger instance
|
|
61
|
+
"""
|
|
62
|
+
self._cache_manager = cache_manager
|
|
63
|
+
self._path_resolver = path_resolver
|
|
64
|
+
self.logger = logger or get_logger("memory_manager")
|
|
65
|
+
|
|
66
|
+
# Memory statistics
|
|
67
|
+
self._stats = {
|
|
68
|
+
"loaded_count": 0,
|
|
69
|
+
"skipped_count": 0,
|
|
70
|
+
"cache_hits": 0,
|
|
71
|
+
"cache_misses": 0,
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
def load_memories(self, agent_name: Optional[str] = None) -> Dict[str, Any]:
|
|
75
|
+
"""
|
|
76
|
+
Load memories for an agent or all agents.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
agent_name: Specific agent name or None for all
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
Dictionary containing:
|
|
83
|
+
- actual_memories: PM memories content
|
|
84
|
+
- agent_memories: Dict of agent-specific memories
|
|
85
|
+
"""
|
|
86
|
+
# Try to get from cache first
|
|
87
|
+
cached_memories = self._cache_manager.get_memories()
|
|
88
|
+
if cached_memories is not None:
|
|
89
|
+
self._stats["cache_hits"] += 1
|
|
90
|
+
self.logger.debug("Memory cache hit")
|
|
91
|
+
|
|
92
|
+
# Filter for specific agent if requested
|
|
93
|
+
if agent_name and "agent_memories" in cached_memories:
|
|
94
|
+
if agent_name in cached_memories["agent_memories"]:
|
|
95
|
+
return {
|
|
96
|
+
"actual_memories": cached_memories.get("actual_memories", ""),
|
|
97
|
+
"agent_memories": {
|
|
98
|
+
agent_name: cached_memories["agent_memories"][agent_name]
|
|
99
|
+
},
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
"actual_memories": cached_memories.get("actual_memories", ""),
|
|
103
|
+
"agent_memories": {},
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return cached_memories
|
|
107
|
+
|
|
108
|
+
# Cache miss - perform actual loading
|
|
109
|
+
self._stats["cache_misses"] += 1
|
|
110
|
+
self.logger.debug("Loading memories from disk (cache miss)")
|
|
111
|
+
|
|
112
|
+
# Reset statistics for this load
|
|
113
|
+
self._stats["loaded_count"] = 0
|
|
114
|
+
self._stats["skipped_count"] = 0
|
|
115
|
+
|
|
116
|
+
# Get deployed agents set (needed to filter agent memories)
|
|
117
|
+
deployed_agents = self._get_deployed_agents()
|
|
118
|
+
|
|
119
|
+
# Load memories from both user and project directories
|
|
120
|
+
result = self._load_actual_memories(deployed_agents)
|
|
121
|
+
|
|
122
|
+
# Cache the loaded memories
|
|
123
|
+
self._cache_manager.set_memories(result)
|
|
124
|
+
|
|
125
|
+
# Filter for specific agent if requested
|
|
126
|
+
if agent_name and "agent_memories" in result:
|
|
127
|
+
if agent_name in result["agent_memories"]:
|
|
128
|
+
return {
|
|
129
|
+
"actual_memories": result.get("actual_memories", ""),
|
|
130
|
+
"agent_memories": {
|
|
131
|
+
agent_name: result["agent_memories"][agent_name]
|
|
132
|
+
},
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
"actual_memories": result.get("actual_memories", ""),
|
|
136
|
+
"agent_memories": {},
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return result
|
|
140
|
+
|
|
141
|
+
def save_memory(
|
|
142
|
+
self, key: str, value: Any, agent_name: Optional[str] = None
|
|
143
|
+
) -> None:
|
|
144
|
+
"""
|
|
145
|
+
Save a memory entry.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
key: Memory key
|
|
149
|
+
value: Memory value
|
|
150
|
+
agent_name: Agent name or None for global
|
|
151
|
+
"""
|
|
152
|
+
# Determine target file
|
|
153
|
+
project_memories_dir = Path.cwd() / ".claude-mpm" / "memories"
|
|
154
|
+
self._path_resolver.ensure_directory(project_memories_dir)
|
|
155
|
+
|
|
156
|
+
if agent_name:
|
|
157
|
+
memory_file = project_memories_dir / f"{agent_name}_memories.md"
|
|
158
|
+
else:
|
|
159
|
+
memory_file = project_memories_dir / "PM_memories.md"
|
|
160
|
+
|
|
161
|
+
# Load existing content or create new
|
|
162
|
+
if memory_file.exists():
|
|
163
|
+
content = memory_file.read_text(encoding="utf-8")
|
|
164
|
+
lines = content.split("\n")
|
|
165
|
+
else:
|
|
166
|
+
lines = [
|
|
167
|
+
f"# {'Agent Memory: ' + agent_name if agent_name else 'PM Memory'}",
|
|
168
|
+
"",
|
|
169
|
+
]
|
|
170
|
+
|
|
171
|
+
# Add new memory as a bullet point
|
|
172
|
+
timestamp = datetime.now().isoformat()
|
|
173
|
+
lines.append(f"- [{timestamp}] {key}: {value}")
|
|
174
|
+
|
|
175
|
+
# Write back
|
|
176
|
+
memory_file.write_text("\n".join(lines), encoding="utf-8")
|
|
177
|
+
|
|
178
|
+
# Clear cache to force reload on next access
|
|
179
|
+
self._cache_manager.clear_memory_caches()
|
|
180
|
+
|
|
181
|
+
self.logger.info(f"Saved memory to {memory_file.name}")
|
|
182
|
+
|
|
183
|
+
def search_memories(
|
|
184
|
+
self, query: str, agent_name: Optional[str] = None
|
|
185
|
+
) -> List[Dict[str, Any]]:
|
|
186
|
+
"""
|
|
187
|
+
Search memories by query.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
query: Search query
|
|
191
|
+
agent_name: Specific agent or None for all
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
List of matching memory entries
|
|
195
|
+
"""
|
|
196
|
+
memories = self.load_memories(agent_name)
|
|
197
|
+
results = []
|
|
198
|
+
|
|
199
|
+
query_lower = query.lower()
|
|
200
|
+
|
|
201
|
+
# Search in PM memories
|
|
202
|
+
if memories.get("actual_memories"):
|
|
203
|
+
for line in memories["actual_memories"].split("\n"):
|
|
204
|
+
if line.strip().startswith("-") and query_lower in line.lower():
|
|
205
|
+
results.append(
|
|
206
|
+
{"type": "PM", "content": line.strip(), "agent": None}
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
# Search in agent memories
|
|
210
|
+
if "agent_memories" in memories:
|
|
211
|
+
for agent, content in memories["agent_memories"].items():
|
|
212
|
+
if isinstance(content, str):
|
|
213
|
+
for line in content.split("\n"):
|
|
214
|
+
if line.strip().startswith("-") and query_lower in line.lower():
|
|
215
|
+
results.append(
|
|
216
|
+
{
|
|
217
|
+
"type": "Agent",
|
|
218
|
+
"content": line.strip(),
|
|
219
|
+
"agent": agent,
|
|
220
|
+
}
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
return results
|
|
224
|
+
|
|
225
|
+
def clear_memories(self, agent_name: Optional[str] = None) -> None:
|
|
226
|
+
"""
|
|
227
|
+
Clear memories for an agent or all agents.
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
agent_name: Specific agent or None for all
|
|
231
|
+
"""
|
|
232
|
+
# Clear cache
|
|
233
|
+
self._cache_manager.clear_memory_caches()
|
|
234
|
+
|
|
235
|
+
# Clear files if requested
|
|
236
|
+
project_memories_dir = Path.cwd() / ".claude-mpm" / "memories"
|
|
237
|
+
if not project_memories_dir.exists():
|
|
238
|
+
return
|
|
239
|
+
|
|
240
|
+
if agent_name:
|
|
241
|
+
# Clear specific agent memory
|
|
242
|
+
memory_file = project_memories_dir / f"{agent_name}_memories.md"
|
|
243
|
+
if memory_file.exists():
|
|
244
|
+
memory_file.unlink()
|
|
245
|
+
self.logger.info(f"Cleared memories for agent: {agent_name}")
|
|
246
|
+
else:
|
|
247
|
+
# Clear all memories
|
|
248
|
+
for memory_file in project_memories_dir.glob("*_memories.md"):
|
|
249
|
+
memory_file.unlink()
|
|
250
|
+
self.logger.info(f"Cleared memory file: {memory_file.name}")
|
|
251
|
+
|
|
252
|
+
def get_memory_stats(self) -> Dict[str, Any]:
|
|
253
|
+
"""
|
|
254
|
+
Get memory system statistics.
|
|
255
|
+
|
|
256
|
+
Returns:
|
|
257
|
+
Dictionary with memory statistics
|
|
258
|
+
"""
|
|
259
|
+
memories = self.load_memories()
|
|
260
|
+
|
|
261
|
+
stats = dict(self._stats) # Copy internal stats
|
|
262
|
+
|
|
263
|
+
# Add memory content stats
|
|
264
|
+
stats["pm_memory_size"] = len(
|
|
265
|
+
memories.get("actual_memories", "").encode("utf-8")
|
|
266
|
+
)
|
|
267
|
+
stats["agent_count"] = len(memories.get("agent_memories", {}))
|
|
268
|
+
|
|
269
|
+
if "agent_memories" in memories:
|
|
270
|
+
total_agent_size = 0
|
|
271
|
+
for content in memories["agent_memories"].values():
|
|
272
|
+
if isinstance(content, str):
|
|
273
|
+
total_agent_size += len(content.encode("utf-8"))
|
|
274
|
+
stats["total_agent_memory_size"] = total_agent_size
|
|
275
|
+
|
|
276
|
+
return stats
|
|
277
|
+
|
|
278
|
+
# Internal methods (extracted from framework_loader.py)
|
|
279
|
+
|
|
280
|
+
def _load_actual_memories(self, deployed_agents: Set[str]) -> Dict[str, Any]:
|
|
281
|
+
"""
|
|
282
|
+
Load actual memories from both user and project directories.
|
|
283
|
+
|
|
284
|
+
Args:
|
|
285
|
+
deployed_agents: Set of deployed agent names
|
|
286
|
+
|
|
287
|
+
Returns:
|
|
288
|
+
Dictionary with actual_memories and agent_memories
|
|
289
|
+
"""
|
|
290
|
+
# Define memory directories in priority order
|
|
291
|
+
user_memories_dir = Path.home() / ".claude-mpm" / "memories"
|
|
292
|
+
project_memories_dir = Path.cwd() / ".claude-mpm" / "memories"
|
|
293
|
+
|
|
294
|
+
# Dictionary to store aggregated memories
|
|
295
|
+
pm_memories = []
|
|
296
|
+
agent_memories_dict = {}
|
|
297
|
+
|
|
298
|
+
# Load memories from user directory first
|
|
299
|
+
if user_memories_dir.exists():
|
|
300
|
+
self.logger.info(
|
|
301
|
+
f"Loading user-level memory files from: {user_memories_dir}"
|
|
302
|
+
)
|
|
303
|
+
self._load_memories_from_directory(
|
|
304
|
+
user_memories_dir,
|
|
305
|
+
deployed_agents,
|
|
306
|
+
pm_memories,
|
|
307
|
+
agent_memories_dict,
|
|
308
|
+
"user",
|
|
309
|
+
)
|
|
310
|
+
else:
|
|
311
|
+
self.logger.debug(
|
|
312
|
+
f"No user memories directory found at: {user_memories_dir}"
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
# Load memories from project directory (overrides user memories)
|
|
316
|
+
if project_memories_dir.exists():
|
|
317
|
+
self.logger.info(
|
|
318
|
+
f"Loading project-level memory files from: {project_memories_dir}"
|
|
319
|
+
)
|
|
320
|
+
self._load_memories_from_directory(
|
|
321
|
+
project_memories_dir,
|
|
322
|
+
deployed_agents,
|
|
323
|
+
pm_memories,
|
|
324
|
+
agent_memories_dict,
|
|
325
|
+
"project",
|
|
326
|
+
)
|
|
327
|
+
else:
|
|
328
|
+
self.logger.debug(
|
|
329
|
+
f"No project memories directory found at: {project_memories_dir}"
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
result = {}
|
|
333
|
+
|
|
334
|
+
# Aggregate PM memories
|
|
335
|
+
if pm_memories:
|
|
336
|
+
aggregated_pm = self._aggregate_memories(pm_memories)
|
|
337
|
+
result["actual_memories"] = aggregated_pm
|
|
338
|
+
memory_size = len(aggregated_pm.encode("utf-8"))
|
|
339
|
+
self.logger.info(
|
|
340
|
+
f"Aggregated PM memory ({memory_size:,} bytes) from {len(pm_memories)} source(s)"
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
# Store agent memories (already aggregated per agent)
|
|
344
|
+
if agent_memories_dict:
|
|
345
|
+
result["agent_memories"] = agent_memories_dict
|
|
346
|
+
for agent_name, memory_content in agent_memories_dict.items():
|
|
347
|
+
memory_size = len(memory_content.encode("utf-8"))
|
|
348
|
+
self.logger.debug(
|
|
349
|
+
f"Aggregated {agent_name} memory: {memory_size:,} bytes"
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
# Log summary
|
|
353
|
+
if self._stats["loaded_count"] > 0 or self._stats["skipped_count"] > 0:
|
|
354
|
+
agent_count = len(agent_memories_dict) if agent_memories_dict else 0
|
|
355
|
+
pm_loaded = bool(result.get("actual_memories"))
|
|
356
|
+
|
|
357
|
+
summary_parts = []
|
|
358
|
+
if pm_loaded:
|
|
359
|
+
summary_parts.append("PM memory loaded")
|
|
360
|
+
if agent_count > 0:
|
|
361
|
+
summary_parts.append(f"{agent_count} agent memories loaded")
|
|
362
|
+
if self._stats["skipped_count"] > 0:
|
|
363
|
+
summary_parts.append(
|
|
364
|
+
f"{self._stats['skipped_count']} non-deployed agent memories skipped"
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
self.logger.info(f"Memory loading complete: {' | '.join(summary_parts)}")
|
|
368
|
+
|
|
369
|
+
if len(deployed_agents) > 0:
|
|
370
|
+
self.logger.debug(
|
|
371
|
+
f"Deployed agents available for memory loading: {', '.join(sorted(deployed_agents))}"
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
return result
|
|
375
|
+
|
|
376
|
+
def _load_memories_from_directory(
|
|
377
|
+
self,
|
|
378
|
+
memories_dir: Path,
|
|
379
|
+
deployed_agents: Set[str],
|
|
380
|
+
pm_memories: List[Dict[str, Any]],
|
|
381
|
+
agent_memories_dict: Dict[str, Any],
|
|
382
|
+
source: str,
|
|
383
|
+
) -> None:
|
|
384
|
+
"""
|
|
385
|
+
Load memories from a specific directory.
|
|
386
|
+
|
|
387
|
+
Args:
|
|
388
|
+
memories_dir: Directory to load memories from
|
|
389
|
+
deployed_agents: Set of deployed agent names
|
|
390
|
+
pm_memories: List to append PM memories to
|
|
391
|
+
agent_memories_dict: Dict to store agent memories
|
|
392
|
+
source: Source label ("user" or "project")
|
|
393
|
+
"""
|
|
394
|
+
# Load PM memories (always loaded)
|
|
395
|
+
pm_memory_path = memories_dir / "PM_memories.md"
|
|
396
|
+
old_pm_path = memories_dir / "PM.md"
|
|
397
|
+
|
|
398
|
+
# Migrate from old PM.md if needed
|
|
399
|
+
if not pm_memory_path.exists() and old_pm_path.exists():
|
|
400
|
+
self._migrate_legacy_file(old_pm_path, pm_memory_path)
|
|
401
|
+
|
|
402
|
+
if pm_memory_path.exists():
|
|
403
|
+
try:
|
|
404
|
+
loaded_content = pm_memory_path.read_text(encoding="utf-8")
|
|
405
|
+
if loaded_content:
|
|
406
|
+
pm_memories.append(
|
|
407
|
+
{
|
|
408
|
+
"source": source,
|
|
409
|
+
"content": loaded_content,
|
|
410
|
+
"path": pm_memory_path,
|
|
411
|
+
}
|
|
412
|
+
)
|
|
413
|
+
memory_size = len(loaded_content.encode("utf-8"))
|
|
414
|
+
self.logger.info(
|
|
415
|
+
f"Loaded {source} PM memory: {pm_memory_path} ({memory_size:,} bytes)"
|
|
416
|
+
)
|
|
417
|
+
self._stats["loaded_count"] += 1
|
|
418
|
+
except Exception as e:
|
|
419
|
+
self.logger.error(
|
|
420
|
+
f"Failed to load PM memory from {pm_memory_path}: {e}"
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
# Migrate old format memory files
|
|
424
|
+
for old_file in memories_dir.glob("*.md"):
|
|
425
|
+
# Skip files already in correct format and special files
|
|
426
|
+
if old_file.name.endswith("_memories.md") or old_file.name in [
|
|
427
|
+
"PM.md",
|
|
428
|
+
"README.md",
|
|
429
|
+
]:
|
|
430
|
+
continue
|
|
431
|
+
|
|
432
|
+
# Determine new name based on old format
|
|
433
|
+
if old_file.stem.endswith("_agent"):
|
|
434
|
+
# Old format: {agent_name}_agent.md -> {agent_name}_memories.md
|
|
435
|
+
agent_name = old_file.stem[:-6] # Remove "_agent" suffix
|
|
436
|
+
new_path = memories_dir / f"{agent_name}_memories.md"
|
|
437
|
+
if not new_path.exists():
|
|
438
|
+
self._migrate_legacy_file(old_file, new_path)
|
|
439
|
+
else:
|
|
440
|
+
# Intermediate format: {agent_name}.md -> {agent_name}_memories.md
|
|
441
|
+
agent_name = old_file.stem
|
|
442
|
+
new_path = memories_dir / f"{agent_name}_memories.md"
|
|
443
|
+
if not new_path.exists():
|
|
444
|
+
self._migrate_legacy_file(old_file, new_path)
|
|
445
|
+
|
|
446
|
+
# Load agent memories (only for deployed agents)
|
|
447
|
+
for memory_file in memories_dir.glob("*_memories.md"):
|
|
448
|
+
# Skip PM_memories.md as we already handled it
|
|
449
|
+
if memory_file.name == "PM_memories.md":
|
|
450
|
+
continue
|
|
451
|
+
|
|
452
|
+
# Extract agent name from file (remove "_memories" suffix)
|
|
453
|
+
agent_name = memory_file.stem[:-9] # Remove "_memories" suffix
|
|
454
|
+
|
|
455
|
+
# Check if agent is deployed
|
|
456
|
+
if agent_name in deployed_agents:
|
|
457
|
+
try:
|
|
458
|
+
loaded_content = memory_file.read_text(encoding="utf-8")
|
|
459
|
+
if loaded_content:
|
|
460
|
+
# Store or merge agent memories
|
|
461
|
+
if agent_name not in agent_memories_dict:
|
|
462
|
+
agent_memories_dict[agent_name] = []
|
|
463
|
+
|
|
464
|
+
# If it's a list, append the new memory entry
|
|
465
|
+
if isinstance(agent_memories_dict[agent_name], list):
|
|
466
|
+
agent_memories_dict[agent_name].append(
|
|
467
|
+
{
|
|
468
|
+
"source": source,
|
|
469
|
+
"content": loaded_content,
|
|
470
|
+
"path": memory_file,
|
|
471
|
+
}
|
|
472
|
+
)
|
|
473
|
+
|
|
474
|
+
memory_size = len(loaded_content.encode("utf-8"))
|
|
475
|
+
self.logger.info(
|
|
476
|
+
f"Loaded {source} memory for {agent_name}: {memory_file.name} ({memory_size:,} bytes)"
|
|
477
|
+
)
|
|
478
|
+
self._stats["loaded_count"] += 1
|
|
479
|
+
except Exception as e:
|
|
480
|
+
self.logger.error(
|
|
481
|
+
f"Failed to load agent memory from {memory_file}: {e}"
|
|
482
|
+
)
|
|
483
|
+
else:
|
|
484
|
+
# Log skipped memories
|
|
485
|
+
self.logger.info(
|
|
486
|
+
f"Skipped {source} memory: {memory_file.name} (agent '{agent_name}' not deployed)"
|
|
487
|
+
)
|
|
488
|
+
|
|
489
|
+
# Detect naming mismatches
|
|
490
|
+
alt_name = (
|
|
491
|
+
agent_name.replace("_", "-")
|
|
492
|
+
if "_" in agent_name
|
|
493
|
+
else agent_name.replace("-", "_")
|
|
494
|
+
)
|
|
495
|
+
if alt_name in deployed_agents:
|
|
496
|
+
self.logger.warning(
|
|
497
|
+
f"Naming mismatch detected: Memory file uses '{agent_name}' but deployed agent is '{alt_name}'. "
|
|
498
|
+
f"Consider renaming {memory_file.name} to {alt_name}_memories.md"
|
|
499
|
+
)
|
|
500
|
+
|
|
501
|
+
self._stats["skipped_count"] += 1
|
|
502
|
+
|
|
503
|
+
# Aggregate agent memories for this directory
|
|
504
|
+
for agent_name in list(agent_memories_dict.keys()):
|
|
505
|
+
if (
|
|
506
|
+
isinstance(agent_memories_dict[agent_name], list)
|
|
507
|
+
and agent_memories_dict[agent_name]
|
|
508
|
+
):
|
|
509
|
+
# Aggregate memories for this agent
|
|
510
|
+
aggregated = self._aggregate_memories(agent_memories_dict[agent_name])
|
|
511
|
+
agent_memories_dict[agent_name] = aggregated
|
|
512
|
+
|
|
513
|
+
def _aggregate_memories(self, memory_entries: List[Dict[str, Any]]) -> str:
|
|
514
|
+
"""
|
|
515
|
+
Aggregate multiple memory entries into a single memory string.
|
|
516
|
+
|
|
517
|
+
Strategy:
|
|
518
|
+
- Preserve all unique bullet-point items (lines starting with -)
|
|
519
|
+
- Remove exact duplicates
|
|
520
|
+
- Project-level memories take precedence over user-level
|
|
521
|
+
|
|
522
|
+
Args:
|
|
523
|
+
memory_entries: List of memory entries with source, content, and path
|
|
524
|
+
|
|
525
|
+
Returns:
|
|
526
|
+
Aggregated memory content as a string
|
|
527
|
+
"""
|
|
528
|
+
if not memory_entries:
|
|
529
|
+
return ""
|
|
530
|
+
|
|
531
|
+
# If only one entry, return it as-is
|
|
532
|
+
if len(memory_entries) == 1:
|
|
533
|
+
return memory_entries[0]["content"]
|
|
534
|
+
|
|
535
|
+
# Parse all memories into a simple list
|
|
536
|
+
all_items = {} # Dict to track items and their source
|
|
537
|
+
metadata_lines = []
|
|
538
|
+
agent_id = None
|
|
539
|
+
|
|
540
|
+
for entry in memory_entries:
|
|
541
|
+
content = entry["content"]
|
|
542
|
+
source = entry["source"]
|
|
543
|
+
|
|
544
|
+
for line in content.split("\n"):
|
|
545
|
+
# Check for header to extract agent_id
|
|
546
|
+
if line.startswith("# Agent Memory:"):
|
|
547
|
+
agent_id = line.replace("# Agent Memory:", "").strip()
|
|
548
|
+
# Check for metadata lines
|
|
549
|
+
elif line.startswith("<!-- ") and line.endswith(" -->"):
|
|
550
|
+
# Only keep metadata from project source or if not already present
|
|
551
|
+
if source == "project" or line not in metadata_lines:
|
|
552
|
+
metadata_lines.append(line)
|
|
553
|
+
# Check for list items
|
|
554
|
+
elif line.strip().startswith("-"):
|
|
555
|
+
# Normalize the item for comparison
|
|
556
|
+
item_text = line.strip()
|
|
557
|
+
normalized = item_text.lstrip("- ").strip().lower()
|
|
558
|
+
|
|
559
|
+
# Add item if new or if project source overrides user source
|
|
560
|
+
if normalized not in all_items or source == "project":
|
|
561
|
+
all_items[normalized] = (item_text, source)
|
|
562
|
+
|
|
563
|
+
# Build aggregated content
|
|
564
|
+
lines = []
|
|
565
|
+
|
|
566
|
+
# Add header
|
|
567
|
+
if agent_id:
|
|
568
|
+
lines.append(f"# Agent Memory: {agent_id}")
|
|
569
|
+
else:
|
|
570
|
+
lines.append("# Agent Memory")
|
|
571
|
+
|
|
572
|
+
# Add latest timestamp
|
|
573
|
+
lines.append(f"<!-- Last Updated: {datetime.now().isoformat()}Z -->")
|
|
574
|
+
lines.append("")
|
|
575
|
+
|
|
576
|
+
# Add all unique items (sorted for consistency)
|
|
577
|
+
for normalized_key in sorted(all_items.keys()):
|
|
578
|
+
item_text, _ = all_items[normalized_key]
|
|
579
|
+
lines.append(item_text)
|
|
580
|
+
|
|
581
|
+
return "\n".join(lines)
|
|
582
|
+
|
|
583
|
+
def _migrate_legacy_file(self, old_path: Path, new_path: Path) -> None:
|
|
584
|
+
"""
|
|
585
|
+
Migrate memory file from old naming convention to new.
|
|
586
|
+
|
|
587
|
+
WHY: Supports backward compatibility by automatically migrating from
|
|
588
|
+
the old {agent_id}_agent.md and {agent_id}.md formats to the new
|
|
589
|
+
{agent_id}_memories.md format.
|
|
590
|
+
|
|
591
|
+
Args:
|
|
592
|
+
old_path: Path to the old file
|
|
593
|
+
new_path: Path to the new file
|
|
594
|
+
"""
|
|
595
|
+
if old_path.exists() and not new_path.exists():
|
|
596
|
+
try:
|
|
597
|
+
# Read content from old file
|
|
598
|
+
content = old_path.read_text(encoding="utf-8")
|
|
599
|
+
# Write to new file
|
|
600
|
+
new_path.write_text(content, encoding="utf-8")
|
|
601
|
+
# Remove old file
|
|
602
|
+
old_path.unlink()
|
|
603
|
+
self.logger.info(
|
|
604
|
+
f"Migrated memory file from {old_path.name} to {new_path.name}"
|
|
605
|
+
)
|
|
606
|
+
except Exception as e:
|
|
607
|
+
self.logger.error(f"Failed to migrate memory file {old_path.name}: {e}")
|
|
608
|
+
|
|
609
|
+
def _get_deployed_agents(self) -> Set[str]:
|
|
610
|
+
"""
|
|
611
|
+
Get a set of deployed agent names from .claude/agents/ directories.
|
|
612
|
+
|
|
613
|
+
Returns:
|
|
614
|
+
Set of agent names (file stems) that are deployed
|
|
615
|
+
"""
|
|
616
|
+
# Try to get from cache first
|
|
617
|
+
cached = self._cache_manager.get_deployed_agents()
|
|
618
|
+
if cached is not None:
|
|
619
|
+
return cached
|
|
620
|
+
|
|
621
|
+
# Cache miss - perform actual scan
|
|
622
|
+
self.logger.debug("Scanning for deployed agents (cache miss)")
|
|
623
|
+
deployed = set()
|
|
624
|
+
|
|
625
|
+
# Check project-level .claude/agents/
|
|
626
|
+
project_agents_dir = Path.cwd() / ".claude" / "agents"
|
|
627
|
+
if project_agents_dir.exists():
|
|
628
|
+
for agent_file in project_agents_dir.glob("*.md"):
|
|
629
|
+
agent_name = agent_file.stem
|
|
630
|
+
if agent_name.upper() != "README":
|
|
631
|
+
deployed.add(agent_name)
|
|
632
|
+
self.logger.debug(f"Found deployed agent: {agent_name}")
|
|
633
|
+
|
|
634
|
+
# Cache the result
|
|
635
|
+
self._cache_manager.set_deployed_agents(deployed)
|
|
636
|
+
|
|
637
|
+
return deployed
|