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,587 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unified log management with async operations and time-based retention.
|
|
3
|
+
|
|
4
|
+
WHY: This module consolidates all logging functionality across the codebase,
|
|
5
|
+
providing a single source of truth for log management with async operations,
|
|
6
|
+
time-based retention, and prompt logging capabilities.
|
|
7
|
+
|
|
8
|
+
DESIGN DECISIONS:
|
|
9
|
+
- Time-based retention (48 hours default) instead of count-based
|
|
10
|
+
- Async fire-and-forget pattern for non-blocking operations
|
|
11
|
+
- Queue-based writing inspired by AsyncSessionLogger
|
|
12
|
+
- Unified cleanup function to replace duplicate implementations
|
|
13
|
+
- Prompt logging for system and agent prompts
|
|
14
|
+
- Configurable retention periods for different log types
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import asyncio
|
|
18
|
+
import json
|
|
19
|
+
import logging
|
|
20
|
+
import os
|
|
21
|
+
from datetime import datetime, timedelta
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from queue import Full, Queue
|
|
24
|
+
from threading import Lock, Thread
|
|
25
|
+
from typing import Any, Dict, Optional
|
|
26
|
+
|
|
27
|
+
from ..core.config import Config
|
|
28
|
+
from ..core.constants import SystemLimits
|
|
29
|
+
|
|
30
|
+
logger = logging.getLogger(__name__)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class LogManager:
|
|
34
|
+
"""
|
|
35
|
+
Unified log management with async operations and time-based retention.
|
|
36
|
+
|
|
37
|
+
Features:
|
|
38
|
+
- Async fire-and-forget logging operations
|
|
39
|
+
- Time-based retention (48 hours default)
|
|
40
|
+
- Prompt logging for system and agent prompts
|
|
41
|
+
- Consolidated cleanup functions
|
|
42
|
+
- Queue-based async writing for performance
|
|
43
|
+
- Configurable retention periods
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
# Default retention periods (in hours)
|
|
47
|
+
DEFAULT_RETENTION_HOURS = 48
|
|
48
|
+
DEFAULT_STARTUP_RETENTION_HOURS = 48
|
|
49
|
+
DEFAULT_MPM_RETENTION_HOURS = 48
|
|
50
|
+
DEFAULT_PROMPT_RETENTION_HOURS = 168 # 7 days for prompts
|
|
51
|
+
DEFAULT_SESSION_RETENTION_HOURS = 168 # 7 days for sessions
|
|
52
|
+
|
|
53
|
+
def __init__(self, config: Optional[Config] = None):
|
|
54
|
+
"""
|
|
55
|
+
Initialize the LogManager with configuration.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
config: Configuration instance (creates new if not provided)
|
|
59
|
+
"""
|
|
60
|
+
self.config = config or Config()
|
|
61
|
+
self._setup_logging_config()
|
|
62
|
+
|
|
63
|
+
# Queue for async operations
|
|
64
|
+
self.write_queue: Queue = Queue(maxsize=SystemLimits.MAX_QUEUE_SIZE)
|
|
65
|
+
self.cleanup_queue: Queue = Queue(maxsize=100)
|
|
66
|
+
|
|
67
|
+
# Thread management
|
|
68
|
+
self._write_thread: Optional[Thread] = None
|
|
69
|
+
self._cleanup_thread: Optional[Thread] = None
|
|
70
|
+
self._shutdown = False
|
|
71
|
+
self._lock = Lock()
|
|
72
|
+
|
|
73
|
+
# Cache for directory paths
|
|
74
|
+
self._dir_cache: Dict[str, Path] = {}
|
|
75
|
+
|
|
76
|
+
# Start background threads
|
|
77
|
+
self._start_background_threads()
|
|
78
|
+
|
|
79
|
+
def _setup_logging_config(self):
|
|
80
|
+
"""Load and setup logging configuration from config."""
|
|
81
|
+
logging_config = self.config.get("logging", {})
|
|
82
|
+
response_config = self.config.get("response_logging", {})
|
|
83
|
+
|
|
84
|
+
# Get retention periods from config with defaults
|
|
85
|
+
self.retention_hours = {
|
|
86
|
+
"default": logging_config.get(
|
|
87
|
+
"retention_hours", self.DEFAULT_RETENTION_HOURS
|
|
88
|
+
),
|
|
89
|
+
"startup": logging_config.get(
|
|
90
|
+
"startup_retention_hours", self.DEFAULT_STARTUP_RETENTION_HOURS
|
|
91
|
+
),
|
|
92
|
+
"mpm": logging_config.get(
|
|
93
|
+
"mpm_retention_hours", self.DEFAULT_MPM_RETENTION_HOURS
|
|
94
|
+
),
|
|
95
|
+
"prompts": logging_config.get(
|
|
96
|
+
"prompt_retention_hours", self.DEFAULT_PROMPT_RETENTION_HOURS
|
|
97
|
+
),
|
|
98
|
+
"sessions": response_config.get(
|
|
99
|
+
"session_retention_hours", self.DEFAULT_SESSION_RETENTION_HOURS
|
|
100
|
+
),
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
# Base directories
|
|
104
|
+
self.base_log_dir = Path(
|
|
105
|
+
logging_config.get("base_directory", ".claude-mpm/logs")
|
|
106
|
+
)
|
|
107
|
+
if not self.base_log_dir.is_absolute():
|
|
108
|
+
self.base_log_dir = Path.cwd() / self.base_log_dir
|
|
109
|
+
|
|
110
|
+
def _start_background_threads(self):
|
|
111
|
+
"""Start background threads for async operations."""
|
|
112
|
+
with self._lock:
|
|
113
|
+
if not self._write_thread or not self._write_thread.is_alive():
|
|
114
|
+
self._write_thread = Thread(
|
|
115
|
+
target=self._process_write_queue, daemon=True
|
|
116
|
+
)
|
|
117
|
+
self._write_thread.start()
|
|
118
|
+
|
|
119
|
+
if not self._cleanup_thread or not self._cleanup_thread.is_alive():
|
|
120
|
+
self._cleanup_thread = Thread(
|
|
121
|
+
target=self._process_cleanup_queue, daemon=True
|
|
122
|
+
)
|
|
123
|
+
self._cleanup_thread.start()
|
|
124
|
+
|
|
125
|
+
def _process_write_queue(self):
|
|
126
|
+
"""Process write operations from the queue."""
|
|
127
|
+
while not self._shutdown:
|
|
128
|
+
try:
|
|
129
|
+
# Get write operation with timeout
|
|
130
|
+
operation = self.write_queue.get(timeout=1.0)
|
|
131
|
+
if operation is None: # Shutdown signal
|
|
132
|
+
break
|
|
133
|
+
|
|
134
|
+
# Execute write operation
|
|
135
|
+
try:
|
|
136
|
+
operation()
|
|
137
|
+
except Exception as e:
|
|
138
|
+
logger.error(f"Error in write operation: {e}")
|
|
139
|
+
finally:
|
|
140
|
+
self.write_queue.task_done()
|
|
141
|
+
|
|
142
|
+
except:
|
|
143
|
+
continue # Timeout or other error, continue loop
|
|
144
|
+
|
|
145
|
+
def _process_cleanup_queue(self):
|
|
146
|
+
"""Process cleanup operations from the queue."""
|
|
147
|
+
while not self._shutdown:
|
|
148
|
+
try:
|
|
149
|
+
# Get cleanup operation with timeout
|
|
150
|
+
operation = self.cleanup_queue.get(timeout=1.0)
|
|
151
|
+
if operation is None: # Shutdown signal
|
|
152
|
+
break
|
|
153
|
+
|
|
154
|
+
# Execute cleanup operation
|
|
155
|
+
try:
|
|
156
|
+
operation()
|
|
157
|
+
except Exception as e:
|
|
158
|
+
logger.error(f"Error in cleanup operation: {e}")
|
|
159
|
+
finally:
|
|
160
|
+
self.cleanup_queue.task_done()
|
|
161
|
+
|
|
162
|
+
except:
|
|
163
|
+
continue # Timeout or other error, continue loop
|
|
164
|
+
|
|
165
|
+
async def setup_logging(self, log_type: str) -> Path:
|
|
166
|
+
"""
|
|
167
|
+
Unified log setup for all log types.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
log_type: Type of logging to setup (startup, mpm, prompts, sessions)
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
Path to the log directory
|
|
174
|
+
"""
|
|
175
|
+
# Get or create directory for log type
|
|
176
|
+
log_dir = self._get_log_directory(log_type)
|
|
177
|
+
log_dir.mkdir(parents=True, exist_ok=True)
|
|
178
|
+
|
|
179
|
+
# Add to cache
|
|
180
|
+
self._dir_cache[log_type] = log_dir
|
|
181
|
+
|
|
182
|
+
# Schedule cleanup for old logs
|
|
183
|
+
await self.cleanup_old_logs(
|
|
184
|
+
log_dir,
|
|
185
|
+
pattern="*",
|
|
186
|
+
retention_hours=self.retention_hours.get(
|
|
187
|
+
log_type, self.retention_hours["default"]
|
|
188
|
+
),
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
return log_dir
|
|
192
|
+
|
|
193
|
+
def _get_log_directory(self, log_type: str) -> Path:
|
|
194
|
+
"""
|
|
195
|
+
Get the directory path for a specific log type.
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
log_type: Type of log (startup, mpm, prompts, sessions)
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
Path to the log directory
|
|
202
|
+
"""
|
|
203
|
+
if log_type in self._dir_cache:
|
|
204
|
+
return self._dir_cache[log_type]
|
|
205
|
+
|
|
206
|
+
# Map log types to directory names
|
|
207
|
+
dir_mapping = {
|
|
208
|
+
"startup": "startup",
|
|
209
|
+
"mpm": "", # Root of logs directory
|
|
210
|
+
"prompts": "prompts",
|
|
211
|
+
"sessions": "sessions",
|
|
212
|
+
"agents": "agents",
|
|
213
|
+
"system": "system",
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
subdir = dir_mapping.get(log_type, log_type)
|
|
217
|
+
log_dir = self.base_log_dir / subdir if subdir else self.base_log_dir
|
|
218
|
+
|
|
219
|
+
self._dir_cache[log_type] = log_dir
|
|
220
|
+
return log_dir
|
|
221
|
+
|
|
222
|
+
async def cleanup_old_logs(
|
|
223
|
+
self, directory: Path, pattern: str = "*", retention_hours: int = 48
|
|
224
|
+
) -> int:
|
|
225
|
+
"""
|
|
226
|
+
Consolidated cleanup with time-based retention.
|
|
227
|
+
|
|
228
|
+
Removes log files older than the retention period.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
directory: Directory to clean up
|
|
232
|
+
pattern: File pattern to match (default: all files)
|
|
233
|
+
retention_hours: Hours to retain logs (default: 48)
|
|
234
|
+
|
|
235
|
+
Returns:
|
|
236
|
+
Number of files deleted
|
|
237
|
+
"""
|
|
238
|
+
if not directory.exists():
|
|
239
|
+
return 0
|
|
240
|
+
|
|
241
|
+
# Calculate cutoff time
|
|
242
|
+
cutoff_time = datetime.now() - timedelta(hours=retention_hours)
|
|
243
|
+
|
|
244
|
+
# Schedule async cleanup
|
|
245
|
+
deleted_count = await self._async_cleanup(directory, pattern, cutoff_time)
|
|
246
|
+
|
|
247
|
+
if deleted_count > 0:
|
|
248
|
+
logger.info(f"Cleaned up {deleted_count} old log files from {directory}")
|
|
249
|
+
|
|
250
|
+
return deleted_count
|
|
251
|
+
|
|
252
|
+
async def _async_cleanup(
|
|
253
|
+
self, directory: Path, pattern: str, cutoff_time: datetime
|
|
254
|
+
) -> int:
|
|
255
|
+
"""
|
|
256
|
+
Perform async cleanup of old files.
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
directory: Directory to clean
|
|
260
|
+
pattern: File pattern to match
|
|
261
|
+
cutoff_time: Delete files older than this time
|
|
262
|
+
|
|
263
|
+
Returns:
|
|
264
|
+
Number of files deleted
|
|
265
|
+
"""
|
|
266
|
+
deleted_count = 0
|
|
267
|
+
|
|
268
|
+
def cleanup_task():
|
|
269
|
+
nonlocal deleted_count
|
|
270
|
+
try:
|
|
271
|
+
# Find matching files
|
|
272
|
+
if pattern == "*":
|
|
273
|
+
files = list(directory.iterdir())
|
|
274
|
+
else:
|
|
275
|
+
files = list(directory.glob(pattern))
|
|
276
|
+
|
|
277
|
+
for file_path in files:
|
|
278
|
+
if not file_path.is_file():
|
|
279
|
+
continue
|
|
280
|
+
|
|
281
|
+
try:
|
|
282
|
+
# Check file modification time
|
|
283
|
+
mtime = datetime.fromtimestamp(file_path.stat().st_mtime)
|
|
284
|
+
if mtime < cutoff_time:
|
|
285
|
+
file_path.unlink()
|
|
286
|
+
deleted_count += 1
|
|
287
|
+
except Exception as e:
|
|
288
|
+
logger.debug(f"Could not delete {file_path}: {e}")
|
|
289
|
+
except Exception as e:
|
|
290
|
+
logger.error(f"Error during cleanup: {e}")
|
|
291
|
+
|
|
292
|
+
# Run cleanup in thread pool
|
|
293
|
+
loop = asyncio.get_event_loop()
|
|
294
|
+
await loop.run_in_executor(None, cleanup_task)
|
|
295
|
+
|
|
296
|
+
return deleted_count
|
|
297
|
+
|
|
298
|
+
def _sync_cleanup_old_logs(
|
|
299
|
+
self, directory: Path, pattern: str = "*", retention_hours: int = 48
|
|
300
|
+
) -> int:
|
|
301
|
+
"""
|
|
302
|
+
Synchronous version of cleanup for backward compatibility.
|
|
303
|
+
|
|
304
|
+
Args:
|
|
305
|
+
directory: Directory to clean up
|
|
306
|
+
pattern: File pattern to match
|
|
307
|
+
retention_hours: Hours to retain logs
|
|
308
|
+
|
|
309
|
+
Returns:
|
|
310
|
+
Number of files deleted
|
|
311
|
+
"""
|
|
312
|
+
if not directory.exists():
|
|
313
|
+
return 0
|
|
314
|
+
|
|
315
|
+
# Calculate cutoff time
|
|
316
|
+
cutoff_time = datetime.now() - timedelta(hours=retention_hours)
|
|
317
|
+
deleted_count = 0
|
|
318
|
+
|
|
319
|
+
try:
|
|
320
|
+
# Find matching files
|
|
321
|
+
if pattern == "*":
|
|
322
|
+
files = list(directory.iterdir())
|
|
323
|
+
else:
|
|
324
|
+
files = list(directory.glob(pattern))
|
|
325
|
+
|
|
326
|
+
for file_path in files:
|
|
327
|
+
if not file_path.is_file():
|
|
328
|
+
continue
|
|
329
|
+
|
|
330
|
+
try:
|
|
331
|
+
# Check file modification time
|
|
332
|
+
mtime = datetime.fromtimestamp(file_path.stat().st_mtime)
|
|
333
|
+
if mtime < cutoff_time:
|
|
334
|
+
file_path.unlink()
|
|
335
|
+
deleted_count += 1
|
|
336
|
+
except Exception as e:
|
|
337
|
+
logger.debug(f"Could not delete {file_path}: {e}")
|
|
338
|
+
except Exception as e:
|
|
339
|
+
logger.error(f"Error during sync cleanup: {e}")
|
|
340
|
+
|
|
341
|
+
if deleted_count > 0:
|
|
342
|
+
logger.info(f"Cleaned up {deleted_count} old log files from {directory}")
|
|
343
|
+
|
|
344
|
+
return deleted_count
|
|
345
|
+
|
|
346
|
+
async def log_prompt(
|
|
347
|
+
self, prompt_type: str, content: str, metadata: Optional[Dict[str, Any]] = None
|
|
348
|
+
) -> Optional[Path]:
|
|
349
|
+
"""
|
|
350
|
+
Save prompts to prompts directory.
|
|
351
|
+
|
|
352
|
+
Args:
|
|
353
|
+
prompt_type: Type of prompt (system, agent, custom)
|
|
354
|
+
content: The prompt content
|
|
355
|
+
metadata: Additional metadata to save with prompt
|
|
356
|
+
|
|
357
|
+
Returns:
|
|
358
|
+
Path to the saved prompt file, or None if failed
|
|
359
|
+
"""
|
|
360
|
+
try:
|
|
361
|
+
# Setup prompts directory
|
|
362
|
+
prompts_dir = await self.setup_logging("prompts")
|
|
363
|
+
|
|
364
|
+
# Generate filename with timestamp
|
|
365
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S_%f")[
|
|
366
|
+
:-3
|
|
367
|
+
] # Microseconds to milliseconds
|
|
368
|
+
|
|
369
|
+
# Sanitize prompt type for filename
|
|
370
|
+
if prompt_type is None:
|
|
371
|
+
prompt_type = "unknown"
|
|
372
|
+
safe_type = str(prompt_type).replace(" ", "_").replace("/", "_")
|
|
373
|
+
|
|
374
|
+
# Handle None content
|
|
375
|
+
if content is None:
|
|
376
|
+
content = ""
|
|
377
|
+
|
|
378
|
+
# Determine file extension based on content
|
|
379
|
+
if content and (
|
|
380
|
+
content.strip().startswith("{") or content.strip().startswith("[")
|
|
381
|
+
):
|
|
382
|
+
extension = ".json"
|
|
383
|
+
else:
|
|
384
|
+
extension = ".md"
|
|
385
|
+
|
|
386
|
+
filename = f"{safe_type}_{timestamp}{extension}"
|
|
387
|
+
file_path = prompts_dir / filename
|
|
388
|
+
|
|
389
|
+
# Prepare prompt data
|
|
390
|
+
prompt_data = {
|
|
391
|
+
"timestamp": datetime.now().isoformat(),
|
|
392
|
+
"type": prompt_type,
|
|
393
|
+
"content": content,
|
|
394
|
+
"metadata": metadata or {},
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
# Add session ID if available
|
|
398
|
+
if "session_id" in os.environ:
|
|
399
|
+
prompt_data["session_id"] = os.environ.get("session_id")
|
|
400
|
+
|
|
401
|
+
# Queue async write
|
|
402
|
+
await self._queue_write(file_path, prompt_data, extension)
|
|
403
|
+
|
|
404
|
+
logger.debug(f"Queued prompt logging to {file_path}")
|
|
405
|
+
return file_path
|
|
406
|
+
|
|
407
|
+
except Exception as e:
|
|
408
|
+
logger.error(f"Failed to log prompt: {e}")
|
|
409
|
+
return None
|
|
410
|
+
|
|
411
|
+
async def _queue_write(self, file_path: Path, data: Any, extension: str):
|
|
412
|
+
"""
|
|
413
|
+
Queue a write operation for async processing.
|
|
414
|
+
|
|
415
|
+
Args:
|
|
416
|
+
file_path: Path to write to
|
|
417
|
+
data: Data to write
|
|
418
|
+
extension: File extension to determine format
|
|
419
|
+
"""
|
|
420
|
+
|
|
421
|
+
def write_task():
|
|
422
|
+
try:
|
|
423
|
+
file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
424
|
+
|
|
425
|
+
if extension == ".json":
|
|
426
|
+
# JSON files also get structured metadata for consistency
|
|
427
|
+
with open(file_path, "w", encoding="utf-8") as f:
|
|
428
|
+
json.dump(data, f, indent=2, ensure_ascii=False)
|
|
429
|
+
# For markdown or text files
|
|
430
|
+
elif isinstance(data, dict):
|
|
431
|
+
# Write as formatted markdown with metadata
|
|
432
|
+
with open(file_path, "w", encoding="utf-8") as f:
|
|
433
|
+
f.write("---\n")
|
|
434
|
+
f.write(f"timestamp: {data.get('timestamp', 'unknown')}\n")
|
|
435
|
+
f.write(f"type: {data.get('type', 'unknown')}\n")
|
|
436
|
+
if data.get("session_id"):
|
|
437
|
+
f.write(f"session_id: {data['session_id']}\n")
|
|
438
|
+
if data.get("metadata"):
|
|
439
|
+
f.write(f"metadata: {json.dumps(data['metadata'])}\n")
|
|
440
|
+
f.write("---\n\n")
|
|
441
|
+
f.write(data.get("content", ""))
|
|
442
|
+
else:
|
|
443
|
+
# Write content directly
|
|
444
|
+
with open(file_path, "w", encoding="utf-8") as f:
|
|
445
|
+
f.write(str(data))
|
|
446
|
+
except Exception as e:
|
|
447
|
+
logger.error(f"Failed to write {file_path}: {e}")
|
|
448
|
+
|
|
449
|
+
try:
|
|
450
|
+
self.write_queue.put_nowait(write_task)
|
|
451
|
+
except Full:
|
|
452
|
+
# Queue is full, execute synchronously as fallback
|
|
453
|
+
logger.warning("Write queue full, executing synchronously")
|
|
454
|
+
write_task()
|
|
455
|
+
|
|
456
|
+
async def write_log_async(self, message: str, level: str = "INFO"):
|
|
457
|
+
"""
|
|
458
|
+
Async fire-and-forget logging.
|
|
459
|
+
|
|
460
|
+
Args:
|
|
461
|
+
message: Log message to write
|
|
462
|
+
level: Log level (INFO, WARNING, ERROR, DEBUG)
|
|
463
|
+
"""
|
|
464
|
+
timestamp = datetime.now().isoformat()
|
|
465
|
+
log_entry = f"[{timestamp}] [{level}] {message}\n"
|
|
466
|
+
|
|
467
|
+
# Get appropriate log file based on context
|
|
468
|
+
log_dir = self._get_log_directory("mpm")
|
|
469
|
+
log_file = log_dir / f"mpm_{datetime.now().strftime('%Y%m%d')}.log"
|
|
470
|
+
|
|
471
|
+
def write_task():
|
|
472
|
+
try:
|
|
473
|
+
with open(log_file, "a", encoding="utf-8") as f:
|
|
474
|
+
f.write(log_entry)
|
|
475
|
+
except Exception as e:
|
|
476
|
+
logger.error(f"Failed to write log: {e}")
|
|
477
|
+
|
|
478
|
+
try:
|
|
479
|
+
self.write_queue.put_nowait(write_task)
|
|
480
|
+
except Full:
|
|
481
|
+
# Queue full, log to standard logger as fallback
|
|
482
|
+
getattr(logger, level.lower(), logger.info)(message)
|
|
483
|
+
|
|
484
|
+
def cleanup_old_startup_logs(
|
|
485
|
+
self, project_root: Optional[Path] = None, keep_hours: Optional[int] = None
|
|
486
|
+
) -> int:
|
|
487
|
+
"""
|
|
488
|
+
Replacement for the old cleanup_old_startup_logs function.
|
|
489
|
+
|
|
490
|
+
Now uses time-based retention instead of count-based.
|
|
491
|
+
|
|
492
|
+
Args:
|
|
493
|
+
project_root: Root directory for the project
|
|
494
|
+
keep_hours: Hours to keep logs (default from config)
|
|
495
|
+
|
|
496
|
+
Returns:
|
|
497
|
+
Number of log files deleted
|
|
498
|
+
"""
|
|
499
|
+
if keep_hours is None:
|
|
500
|
+
keep_hours = self.retention_hours.get(
|
|
501
|
+
"startup", self.DEFAULT_STARTUP_RETENTION_HOURS
|
|
502
|
+
)
|
|
503
|
+
|
|
504
|
+
if project_root is None:
|
|
505
|
+
project_root = Path.cwd()
|
|
506
|
+
|
|
507
|
+
log_dir = project_root / ".claude-mpm" / "logs" / "startup"
|
|
508
|
+
|
|
509
|
+
# Use synchronous cleanup for compatibility
|
|
510
|
+
return self._sync_cleanup_old_logs(log_dir, "startup-*.log", keep_hours)
|
|
511
|
+
|
|
512
|
+
def cleanup_old_mpm_logs(
|
|
513
|
+
self, log_dir: Optional[Path] = None, keep_hours: Optional[int] = None
|
|
514
|
+
) -> int:
|
|
515
|
+
"""
|
|
516
|
+
Replacement for the old cleanup_old_mpm_logs function.
|
|
517
|
+
|
|
518
|
+
Now uses time-based retention instead of count-based.
|
|
519
|
+
|
|
520
|
+
Args:
|
|
521
|
+
log_dir: Directory containing log files
|
|
522
|
+
keep_hours: Hours to keep logs (default from config)
|
|
523
|
+
|
|
524
|
+
Returns:
|
|
525
|
+
Number of log files deleted
|
|
526
|
+
"""
|
|
527
|
+
if keep_hours is None:
|
|
528
|
+
keep_hours = self.retention_hours.get(
|
|
529
|
+
"mpm", self.DEFAULT_MPM_RETENTION_HOURS
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
if log_dir is None:
|
|
533
|
+
from claude_mpm.core.unified_paths import get_project_root
|
|
534
|
+
|
|
535
|
+
deployment_root = get_project_root()
|
|
536
|
+
log_dir = deployment_root / ".claude-mpm" / "logs"
|
|
537
|
+
|
|
538
|
+
# Use synchronous cleanup for compatibility
|
|
539
|
+
return self._sync_cleanup_old_logs(log_dir, "mpm_*.log", keep_hours)
|
|
540
|
+
|
|
541
|
+
def shutdown(self):
|
|
542
|
+
"""Gracefully shutdown the LogManager."""
|
|
543
|
+
self._shutdown = True
|
|
544
|
+
|
|
545
|
+
# Signal threads to stop
|
|
546
|
+
try:
|
|
547
|
+
self.write_queue.put_nowait(None)
|
|
548
|
+
self.cleanup_queue.put_nowait(None)
|
|
549
|
+
except:
|
|
550
|
+
pass
|
|
551
|
+
|
|
552
|
+
# Wait for threads to finish
|
|
553
|
+
if self._write_thread and self._write_thread.is_alive():
|
|
554
|
+
self._write_thread.join(timeout=2.0)
|
|
555
|
+
|
|
556
|
+
if self._cleanup_thread and self._cleanup_thread.is_alive():
|
|
557
|
+
self._cleanup_thread.join(timeout=2.0)
|
|
558
|
+
|
|
559
|
+
def __enter__(self):
|
|
560
|
+
"""Context manager entry."""
|
|
561
|
+
return self
|
|
562
|
+
|
|
563
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
564
|
+
"""Context manager exit."""
|
|
565
|
+
self.shutdown()
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
# Global singleton instance
|
|
569
|
+
_log_manager_instance: Optional[LogManager] = None
|
|
570
|
+
_log_manager_lock = Lock()
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
def get_log_manager() -> LogManager:
|
|
574
|
+
"""
|
|
575
|
+
Get or create the global LogManager instance.
|
|
576
|
+
|
|
577
|
+
Returns:
|
|
578
|
+
The global LogManager instance
|
|
579
|
+
"""
|
|
580
|
+
global _log_manager_instance
|
|
581
|
+
|
|
582
|
+
if _log_manager_instance is None:
|
|
583
|
+
with _log_manager_lock:
|
|
584
|
+
if _log_manager_instance is None:
|
|
585
|
+
_log_manager_instance = LogManager()
|
|
586
|
+
|
|
587
|
+
return _log_manager_instance
|