claude-mpm 4.1.1__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 +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 +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.1.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.1.dist-info/RECORD +0 -494
- {claude_mpm-4.1.1.dist-info → claude_mpm-4.1.2.dist-info}/WHEEL +0 -0
- {claude_mpm-4.1.1.dist-info → claude_mpm-4.1.2.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.1.1.dist-info → claude_mpm-4.1.2.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.1.1.dist-info → claude_mpm-4.1.2.dist-info}/top_level.txt +0 -0
|
@@ -13,7 +13,7 @@ import socket
|
|
|
13
13
|
import subprocess
|
|
14
14
|
import time
|
|
15
15
|
from pathlib import Path
|
|
16
|
-
from typing import Dict, List, Optional, Tuple
|
|
16
|
+
from typing import Dict, List, NamedTuple, Optional, Tuple
|
|
17
17
|
|
|
18
18
|
import psutil
|
|
19
19
|
|
|
@@ -22,6 +22,7 @@ from ..core.logging_config import get_logger
|
|
|
22
22
|
|
|
23
23
|
class ProcessInfo(NamedTuple):
|
|
24
24
|
"""Information about a process using a port."""
|
|
25
|
+
|
|
25
26
|
pid: int
|
|
26
27
|
name: str
|
|
27
28
|
cmdline: str
|
|
@@ -49,18 +50,18 @@ class PortManager:
|
|
|
49
50
|
try:
|
|
50
51
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
|
51
52
|
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
52
|
-
|
|
53
|
+
sock.bind(("localhost", port))
|
|
53
54
|
return True
|
|
54
55
|
except OSError:
|
|
55
56
|
return False
|
|
56
|
-
|
|
57
|
+
|
|
57
58
|
def get_process_on_port(self, port: int) -> Optional[ProcessInfo]:
|
|
58
59
|
"""Get information about the process using a specific port.
|
|
59
|
-
|
|
60
|
+
|
|
60
61
|
WHY: We need to identify what process is using a port to make intelligent
|
|
61
62
|
decisions about whether we can reclaim it (our debug scripts) or must
|
|
62
63
|
avoid it (external processes or our daemons).
|
|
63
|
-
|
|
64
|
+
|
|
64
65
|
Returns:
|
|
65
66
|
ProcessInfo with details about the process, or None if port is free
|
|
66
67
|
"""
|
|
@@ -68,32 +69,35 @@ class PortManager:
|
|
|
68
69
|
# First try using lsof as it's more reliable for port detection
|
|
69
70
|
try:
|
|
70
71
|
result = subprocess.run(
|
|
71
|
-
[
|
|
72
|
+
["lsof", "-i", f":{port}", "-sTCP:LISTEN", "-t"],
|
|
72
73
|
capture_output=True,
|
|
73
74
|
text=True,
|
|
74
|
-
timeout=2
|
|
75
|
+
timeout=2,
|
|
76
|
+
check=False,
|
|
75
77
|
)
|
|
76
78
|
if result.returncode == 0 and result.stdout.strip():
|
|
77
79
|
# Get the PID from lsof output
|
|
78
80
|
pid = int(result.stdout.strip().split()[0])
|
|
79
81
|
try:
|
|
80
82
|
process = psutil.Process(pid)
|
|
81
|
-
cmdline =
|
|
82
|
-
|
|
83
|
+
cmdline = " ".join(process.cmdline())
|
|
84
|
+
|
|
83
85
|
# Determine if this is our process and what type
|
|
84
86
|
is_ours = self._is_our_process(pid, cmdline)
|
|
85
87
|
is_debug = self._is_debug_process(cmdline) if is_ours else False
|
|
86
|
-
is_daemon =
|
|
87
|
-
|
|
88
|
+
is_daemon = (
|
|
89
|
+
self._is_daemon_process(cmdline) if is_ours else False
|
|
90
|
+
)
|
|
91
|
+
|
|
88
92
|
return ProcessInfo(
|
|
89
93
|
pid=pid,
|
|
90
94
|
name=process.name(),
|
|
91
95
|
cmdline=cmdline,
|
|
92
96
|
is_ours=is_ours,
|
|
93
97
|
is_debug=is_debug,
|
|
94
|
-
is_daemon=is_daemon
|
|
98
|
+
is_daemon=is_daemon,
|
|
95
99
|
)
|
|
96
|
-
except (psutil.NoSuchProcess, psutil.AccessDenied)
|
|
100
|
+
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
|
97
101
|
# Process exists but we can't access it
|
|
98
102
|
return ProcessInfo(
|
|
99
103
|
pid=pid,
|
|
@@ -101,31 +105,33 @@ class PortManager:
|
|
|
101
105
|
cmdline="<permission denied>",
|
|
102
106
|
is_ours=False,
|
|
103
107
|
is_debug=False,
|
|
104
|
-
is_daemon=False
|
|
108
|
+
is_daemon=False,
|
|
105
109
|
)
|
|
106
110
|
except (subprocess.TimeoutExpired, FileNotFoundError):
|
|
107
111
|
# lsof not available or timed out, fall back to psutil
|
|
108
112
|
pass
|
|
109
|
-
|
|
113
|
+
|
|
110
114
|
# Fallback to psutil method
|
|
111
|
-
for conn in psutil.net_connections(kind=
|
|
112
|
-
if conn.laddr.port == port and conn.status ==
|
|
115
|
+
for conn in psutil.net_connections(kind="inet"):
|
|
116
|
+
if conn.laddr.port == port and conn.status == "LISTEN":
|
|
113
117
|
try:
|
|
114
118
|
process = psutil.Process(conn.pid)
|
|
115
|
-
cmdline =
|
|
116
|
-
|
|
119
|
+
cmdline = " ".join(process.cmdline())
|
|
120
|
+
|
|
117
121
|
# Determine if this is our process and what type
|
|
118
122
|
is_ours = self._is_our_process(conn.pid, cmdline)
|
|
119
123
|
is_debug = self._is_debug_process(cmdline) if is_ours else False
|
|
120
|
-
is_daemon =
|
|
121
|
-
|
|
124
|
+
is_daemon = (
|
|
125
|
+
self._is_daemon_process(cmdline) if is_ours else False
|
|
126
|
+
)
|
|
127
|
+
|
|
122
128
|
return ProcessInfo(
|
|
123
129
|
pid=conn.pid,
|
|
124
130
|
name=process.name(),
|
|
125
131
|
cmdline=cmdline,
|
|
126
132
|
is_ours=is_ours,
|
|
127
133
|
is_debug=is_debug,
|
|
128
|
-
is_daemon=is_daemon
|
|
134
|
+
is_daemon=is_daemon,
|
|
129
135
|
)
|
|
130
136
|
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
|
131
137
|
# Can't access process details, mark as unknown external
|
|
@@ -135,7 +141,7 @@ class PortManager:
|
|
|
135
141
|
cmdline="<permission denied>",
|
|
136
142
|
is_ours=False,
|
|
137
143
|
is_debug=False,
|
|
138
|
-
is_daemon=False
|
|
144
|
+
is_daemon=False,
|
|
139
145
|
)
|
|
140
146
|
except psutil.AccessDenied:
|
|
141
147
|
# No permission to check network connections
|
|
@@ -148,158 +154,164 @@ class PortManager:
|
|
|
148
154
|
cmdline="<unable to determine>",
|
|
149
155
|
is_ours=False,
|
|
150
156
|
is_debug=False,
|
|
151
|
-
is_daemon=False
|
|
157
|
+
is_daemon=False,
|
|
152
158
|
)
|
|
153
159
|
except Exception as e:
|
|
154
160
|
self.logger.debug(f"Error getting process on port {port}: {e}")
|
|
155
|
-
|
|
161
|
+
|
|
156
162
|
return None
|
|
157
|
-
|
|
158
|
-
def _is_our_process(self, pid: int, cmdline: str = None) -> bool:
|
|
163
|
+
|
|
164
|
+
def _is_our_process(self, pid: int, cmdline: Optional[str] = None) -> bool:
|
|
159
165
|
"""Check if a process belongs to claude-mpm.
|
|
160
|
-
|
|
166
|
+
|
|
161
167
|
WHY: We need to distinguish our processes from external ones to know
|
|
162
168
|
which ports we can potentially reclaim.
|
|
163
169
|
"""
|
|
164
170
|
try:
|
|
165
171
|
if cmdline is None:
|
|
166
172
|
process = psutil.Process(pid)
|
|
167
|
-
cmdline =
|
|
168
|
-
|
|
173
|
+
cmdline = " ".join(process.cmdline())
|
|
174
|
+
|
|
169
175
|
cmdline_lower = cmdline.lower()
|
|
170
|
-
|
|
176
|
+
|
|
171
177
|
# Check for claude-mpm related patterns
|
|
172
178
|
our_patterns = [
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
179
|
+
"claude-mpm",
|
|
180
|
+
"claude_mpm",
|
|
181
|
+
"socketio_debug",
|
|
182
|
+
"socketio_daemon",
|
|
183
|
+
"socketio_server",
|
|
178
184
|
str(self.project_root).lower(), # Running from our project directory
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
185
|
+
"scripts/test_", # Our test scripts
|
|
186
|
+
"scripts/debug_", # Our debug scripts
|
|
187
|
+
"scripts/demo_", # Our demo scripts
|
|
188
|
+
"scripts/run_", # Our run scripts
|
|
189
|
+
"scripts/validate_", # Our validation scripts
|
|
184
190
|
]
|
|
185
|
-
|
|
191
|
+
|
|
186
192
|
return any(pattern in cmdline_lower for pattern in our_patterns)
|
|
187
|
-
|
|
193
|
+
|
|
188
194
|
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
|
189
195
|
return False
|
|
190
|
-
|
|
196
|
+
|
|
191
197
|
def _is_debug_process(self, cmdline: str) -> bool:
|
|
192
198
|
"""Check if a process is a debug/test script (safe to kill).
|
|
193
|
-
|
|
199
|
+
|
|
194
200
|
WHY: Debug and test scripts can be safely terminated to reclaim ports,
|
|
195
201
|
unlike production daemons which should be preserved.
|
|
196
202
|
"""
|
|
197
203
|
cmdline_lower = cmdline.lower()
|
|
198
|
-
|
|
204
|
+
|
|
199
205
|
debug_patterns = [
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
206
|
+
"socketio_debug.py",
|
|
207
|
+
"run_socketio_debug.py",
|
|
208
|
+
"test_",
|
|
209
|
+
"debug_",
|
|
210
|
+
"demo_",
|
|
211
|
+
"validate_",
|
|
212
|
+
"scripts/test",
|
|
213
|
+
"scripts/debug",
|
|
214
|
+
"scripts/demo",
|
|
215
|
+
"scripts/validate",
|
|
210
216
|
]
|
|
211
|
-
|
|
217
|
+
|
|
212
218
|
# Also check if NOT a daemon (daemons are not debug scripts)
|
|
213
|
-
is_not_daemon =
|
|
214
|
-
|
|
215
|
-
return
|
|
216
|
-
|
|
219
|
+
is_not_daemon = "daemon" not in cmdline_lower or "debug" in cmdline_lower
|
|
220
|
+
|
|
221
|
+
return (
|
|
222
|
+
any(pattern in cmdline_lower for pattern in debug_patterns)
|
|
223
|
+
and is_not_daemon
|
|
224
|
+
)
|
|
225
|
+
|
|
217
226
|
def _is_daemon_process(self, cmdline: str) -> bool:
|
|
218
227
|
"""Check if a process is a daemon (should be preserved).
|
|
219
|
-
|
|
228
|
+
|
|
220
229
|
WHY: Daemon processes are production services that should not be
|
|
221
230
|
automatically killed. Users must explicitly stop them.
|
|
222
231
|
"""
|
|
223
232
|
cmdline_lower = cmdline.lower()
|
|
224
|
-
|
|
233
|
+
|
|
225
234
|
daemon_patterns = [
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
235
|
+
"socketio_daemon",
|
|
236
|
+
"claude-mpm monitor",
|
|
237
|
+
"daemon",
|
|
229
238
|
]
|
|
230
|
-
|
|
239
|
+
|
|
231
240
|
# Exclude debug daemons
|
|
232
|
-
if
|
|
241
|
+
if "debug" in cmdline_lower:
|
|
233
242
|
return False
|
|
234
|
-
|
|
243
|
+
|
|
235
244
|
return any(pattern in cmdline_lower for pattern in daemon_patterns)
|
|
236
|
-
|
|
245
|
+
|
|
237
246
|
def kill_process_on_port(self, port: int, force: bool = False) -> bool:
|
|
238
247
|
"""Kill a process using a specific port if it's safe to do so.
|
|
239
|
-
|
|
248
|
+
|
|
240
249
|
WHY: Automatically reclaim ports from our debug scripts while preserving
|
|
241
250
|
daemons and avoiding external processes.
|
|
242
|
-
|
|
251
|
+
|
|
243
252
|
Args:
|
|
244
253
|
port: Port number to reclaim
|
|
245
254
|
force: If True, kill even daemon processes (requires explicit user action)
|
|
246
|
-
|
|
255
|
+
|
|
247
256
|
Returns:
|
|
248
257
|
True if process was killed or port is now free, False otherwise
|
|
249
258
|
"""
|
|
250
259
|
process_info = self.get_process_on_port(port)
|
|
251
|
-
|
|
260
|
+
|
|
252
261
|
if not process_info:
|
|
253
262
|
self.logger.info(f"Port {port} is already free")
|
|
254
263
|
return True
|
|
255
|
-
|
|
264
|
+
|
|
256
265
|
if not process_info.is_ours:
|
|
257
266
|
self.logger.warning(
|
|
258
267
|
f"Port {port} is used by external process '{process_info.name}' "
|
|
259
268
|
f"(PID: {process_info.pid}). Cannot reclaim."
|
|
260
269
|
)
|
|
261
270
|
return False
|
|
262
|
-
|
|
271
|
+
|
|
263
272
|
if process_info.is_daemon and not force:
|
|
264
273
|
self.logger.warning(
|
|
265
274
|
f"Port {port} is used by our daemon process (PID: {process_info.pid}). "
|
|
266
275
|
f"Use --force flag or stop the daemon explicitly."
|
|
267
276
|
)
|
|
268
277
|
return False
|
|
269
|
-
|
|
278
|
+
|
|
270
279
|
if process_info.is_debug or force:
|
|
271
280
|
try:
|
|
272
281
|
self.logger.info(
|
|
273
282
|
f"Killing {'debug' if process_info.is_debug else 'daemon'} process "
|
|
274
283
|
f"{process_info.pid} on port {port}"
|
|
275
284
|
)
|
|
276
|
-
|
|
285
|
+
|
|
277
286
|
# Try graceful termination first
|
|
278
287
|
os.kill(process_info.pid, signal.SIGTERM)
|
|
279
|
-
|
|
288
|
+
|
|
280
289
|
# Wait up to 2 seconds for graceful shutdown
|
|
281
290
|
for _ in range(20):
|
|
282
291
|
time.sleep(0.1)
|
|
283
292
|
if not psutil.pid_exists(process_info.pid):
|
|
284
|
-
self.logger.info(
|
|
293
|
+
self.logger.info(
|
|
294
|
+
f"Process {process_info.pid} terminated gracefully"
|
|
295
|
+
)
|
|
285
296
|
return True
|
|
286
|
-
|
|
297
|
+
|
|
287
298
|
# Force kill if still running
|
|
288
|
-
self.logger.warning(
|
|
299
|
+
self.logger.warning(
|
|
300
|
+
f"Process {process_info.pid} didn't terminate, forcing kill"
|
|
301
|
+
)
|
|
289
302
|
os.kill(process_info.pid, signal.SIGKILL)
|
|
290
303
|
time.sleep(0.5)
|
|
291
|
-
|
|
304
|
+
|
|
292
305
|
if not psutil.pid_exists(process_info.pid):
|
|
293
306
|
self.logger.info(f"Process {process_info.pid} force killed")
|
|
294
307
|
return True
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
308
|
+
self.logger.error(f"Failed to kill process {process_info.pid}")
|
|
309
|
+
return False
|
|
310
|
+
|
|
299
311
|
except Exception as e:
|
|
300
312
|
self.logger.error(f"Error killing process {process_info.pid}: {e}")
|
|
301
313
|
return False
|
|
302
|
-
|
|
314
|
+
|
|
303
315
|
return False
|
|
304
316
|
|
|
305
317
|
def is_claude_mpm_instance(self, port: int) -> Tuple[bool, Optional[Dict]]:
|
|
@@ -344,14 +356,14 @@ class PortManager:
|
|
|
344
356
|
self, preferred_port: Optional[int] = None, reclaim: bool = True
|
|
345
357
|
) -> Optional[int]:
|
|
346
358
|
"""Find an available port, preferring the specified port if given.
|
|
347
|
-
|
|
359
|
+
|
|
348
360
|
WHY: Enhanced to intelligently reclaim ports from our debug processes
|
|
349
361
|
while avoiding external processes and preserving daemons.
|
|
350
|
-
|
|
362
|
+
|
|
351
363
|
Args:
|
|
352
364
|
preferred_port: Port to try first
|
|
353
365
|
reclaim: If True, try to reclaim ports from our debug scripts
|
|
354
|
-
|
|
366
|
+
|
|
355
367
|
Returns:
|
|
356
368
|
Available port number or None if no ports available
|
|
357
369
|
"""
|
|
@@ -359,7 +371,7 @@ class PortManager:
|
|
|
359
371
|
if preferred_port and preferred_port in self.PORT_RANGE:
|
|
360
372
|
if self.is_port_available(preferred_port):
|
|
361
373
|
return preferred_port
|
|
362
|
-
|
|
374
|
+
|
|
363
375
|
# Port is in use - check if we can reclaim it
|
|
364
376
|
if reclaim:
|
|
365
377
|
process_info = self.get_process_on_port(preferred_port)
|
|
@@ -384,7 +396,7 @@ class PortManager:
|
|
|
384
396
|
# Try default port
|
|
385
397
|
if self.is_port_available(self.DEFAULT_PORT):
|
|
386
398
|
return self.DEFAULT_PORT
|
|
387
|
-
|
|
399
|
+
|
|
388
400
|
# Check if we can reclaim default port
|
|
389
401
|
if reclaim:
|
|
390
402
|
process_info = self.get_process_on_port(self.DEFAULT_PORT)
|
|
@@ -404,7 +416,7 @@ class PortManager:
|
|
|
404
416
|
|
|
405
417
|
if self.is_port_available(port):
|
|
406
418
|
return port
|
|
407
|
-
|
|
419
|
+
|
|
408
420
|
# Try to reclaim if it's our debug process
|
|
409
421
|
if reclaim:
|
|
410
422
|
process_info = self.get_process_on_port(port)
|
|
@@ -462,7 +474,7 @@ class PortManager:
|
|
|
462
474
|
"""Load registered instances from file."""
|
|
463
475
|
try:
|
|
464
476
|
if self.instances_file.exists():
|
|
465
|
-
with open(self.instances_file
|
|
477
|
+
with open(self.instances_file) as f:
|
|
466
478
|
return json.load(f)
|
|
467
479
|
except Exception as e:
|
|
468
480
|
self.logger.warning(f"Failed to load instances file: {e}")
|
|
@@ -522,13 +534,13 @@ class PortManager:
|
|
|
522
534
|
return instance_info
|
|
523
535
|
|
|
524
536
|
return None
|
|
525
|
-
|
|
537
|
+
|
|
526
538
|
def get_port_status(self, port: int) -> Dict[str, any]:
|
|
527
539
|
"""Get detailed status of a port including what's using it.
|
|
528
|
-
|
|
540
|
+
|
|
529
541
|
WHY: Provides comprehensive information for users to understand
|
|
530
542
|
port conflicts and make informed decisions.
|
|
531
|
-
|
|
543
|
+
|
|
532
544
|
Returns:
|
|
533
545
|
Dictionary with port status details
|
|
534
546
|
"""
|
|
@@ -537,9 +549,9 @@ class PortManager:
|
|
|
537
549
|
"available": self.is_port_available(port),
|
|
538
550
|
"process": None,
|
|
539
551
|
"instance": None,
|
|
540
|
-
"recommendation": None
|
|
552
|
+
"recommendation": None,
|
|
541
553
|
}
|
|
542
|
-
|
|
554
|
+
|
|
543
555
|
# Check for process using the port
|
|
544
556
|
process_info = self.get_process_on_port(port)
|
|
545
557
|
if process_info:
|
|
@@ -549,27 +561,37 @@ class PortManager:
|
|
|
549
561
|
"is_ours": process_info.is_ours,
|
|
550
562
|
"is_debug": process_info.is_debug,
|
|
551
563
|
"is_daemon": process_info.is_daemon,
|
|
552
|
-
"cmdline":
|
|
564
|
+
"cmdline": (
|
|
565
|
+
process_info.cmdline[:100] + "..."
|
|
566
|
+
if len(process_info.cmdline) > 100
|
|
567
|
+
else process_info.cmdline
|
|
568
|
+
),
|
|
553
569
|
}
|
|
554
|
-
|
|
570
|
+
|
|
555
571
|
# Provide recommendation based on process type
|
|
556
572
|
if process_info.is_ours:
|
|
557
573
|
if process_info.is_debug:
|
|
558
|
-
status["recommendation"] =
|
|
574
|
+
status["recommendation"] = (
|
|
575
|
+
"Can be automatically reclaimed (debug process)"
|
|
576
|
+
)
|
|
559
577
|
elif process_info.is_daemon:
|
|
560
|
-
status["recommendation"] =
|
|
578
|
+
status["recommendation"] = (
|
|
579
|
+
"Stop daemon with 'claude-mpm monitor stop' or use --force"
|
|
580
|
+
)
|
|
561
581
|
else:
|
|
562
|
-
status["recommendation"] =
|
|
582
|
+
status["recommendation"] = (
|
|
583
|
+
"Our process, consider stopping it manually"
|
|
584
|
+
)
|
|
563
585
|
else:
|
|
564
586
|
status["recommendation"] = "External process, choose a different port"
|
|
565
|
-
|
|
587
|
+
|
|
566
588
|
# Check for registered instance
|
|
567
589
|
instance_info = self.get_instance_by_port(port)
|
|
568
590
|
if instance_info:
|
|
569
591
|
status["instance"] = {
|
|
570
592
|
"id": instance_info.get("instance_id"),
|
|
571
593
|
"pid": instance_info.get("pid"),
|
|
572
|
-
"start_time": instance_info.get("start_time")
|
|
594
|
+
"start_time": instance_info.get("start_time"),
|
|
573
595
|
}
|
|
574
|
-
|
|
596
|
+
|
|
575
597
|
return status
|
|
@@ -25,10 +25,10 @@ This service analyzes:
|
|
|
25
25
|
import json
|
|
26
26
|
import logging
|
|
27
27
|
import re
|
|
28
|
-
from collections import Counter
|
|
28
|
+
from collections import Counter
|
|
29
29
|
from dataclasses import asdict, dataclass
|
|
30
30
|
from pathlib import Path
|
|
31
|
-
from typing import Any, Dict, List, Optional
|
|
31
|
+
from typing import Any, Dict, List, Optional
|
|
32
32
|
|
|
33
33
|
from claude_mpm.core.config import Config
|
|
34
34
|
from claude_mpm.core.interfaces import ProjectAnalyzerInterface
|
|
@@ -304,7 +304,7 @@ class ProjectAnalyzer(ProjectAnalyzerInterface):
|
|
|
304
304
|
) -> None:
|
|
305
305
|
"""Parse package.json for Node.js project details."""
|
|
306
306
|
try:
|
|
307
|
-
with open(package_path
|
|
307
|
+
with open(package_path) as f:
|
|
308
308
|
package_data = json.load(f)
|
|
309
309
|
|
|
310
310
|
# Extract dependencies
|
|
@@ -313,7 +313,7 @@ class ProjectAnalyzer(ProjectAnalyzerInterface):
|
|
|
313
313
|
all_deps.update(package_data.get("devDependencies", {}))
|
|
314
314
|
|
|
315
315
|
# Identify frameworks and tools
|
|
316
|
-
for dep_name in all_deps
|
|
316
|
+
for dep_name in all_deps:
|
|
317
317
|
dep_lower = dep_name.lower()
|
|
318
318
|
|
|
319
319
|
# Web frameworks
|
|
@@ -327,12 +327,14 @@ class ProjectAnalyzer(ProjectAnalyzerInterface):
|
|
|
327
327
|
db in dep_lower for db in ["mysql", "postgres", "mongodb", "redis"]
|
|
328
328
|
):
|
|
329
329
|
characteristics.databases.append(dep_name)
|
|
330
|
-
elif
|
|
331
|
-
|
|
332
|
-
|
|
330
|
+
elif (
|
|
331
|
+
any(
|
|
332
|
+
test in dep_lower
|
|
333
|
+
for test in ["jest", "mocha", "cypress", "playwright"]
|
|
334
|
+
)
|
|
335
|
+
and not characteristics.testing_framework
|
|
333
336
|
):
|
|
334
|
-
|
|
335
|
-
characteristics.testing_framework = dep_name
|
|
337
|
+
characteristics.testing_framework = dep_name
|
|
336
338
|
|
|
337
339
|
characteristics.key_dependencies.append(dep_name)
|
|
338
340
|
|
|
@@ -423,7 +425,7 @@ class ProjectAnalyzer(ProjectAnalyzerInterface):
|
|
|
423
425
|
cargo_data = tomllib.load(f)
|
|
424
426
|
|
|
425
427
|
deps = cargo_data.get("dependencies", {})
|
|
426
|
-
for dep_name in deps
|
|
428
|
+
for dep_name in deps:
|
|
427
429
|
characteristics.key_dependencies.append(dep_name)
|
|
428
430
|
|
|
429
431
|
# Identify common Rust frameworks
|
|
@@ -18,7 +18,6 @@ to provide complete project lifecycle tracking.
|
|
|
18
18
|
|
|
19
19
|
import os
|
|
20
20
|
import platform
|
|
21
|
-
import shutil
|
|
22
21
|
import subprocess
|
|
23
22
|
import sys
|
|
24
23
|
import uuid
|
|
@@ -29,14 +28,11 @@ from typing import Any, Dict, List, Optional
|
|
|
29
28
|
import yaml
|
|
30
29
|
|
|
31
30
|
from claude_mpm.core.logger import get_logger
|
|
32
|
-
from claude_mpm.core.unified_paths import get_project_root
|
|
33
31
|
|
|
34
32
|
|
|
35
33
|
class ProjectRegistryError(Exception):
|
|
36
34
|
"""Base exception for project registry operations."""
|
|
37
35
|
|
|
38
|
-
pass
|
|
39
|
-
|
|
40
36
|
|
|
41
37
|
class ProjectRegistry:
|
|
42
38
|
"""
|
|
@@ -102,10 +98,9 @@ class ProjectRegistry:
|
|
|
102
98
|
)
|
|
103
99
|
# Update existing entry with current session info
|
|
104
100
|
return self._update_existing_entry(existing_entry)
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
return self._create_new_entry()
|
|
101
|
+
self.logger.debug("Creating new project registry entry")
|
|
102
|
+
# Create new entry
|
|
103
|
+
return self._create_new_entry()
|
|
109
104
|
|
|
110
105
|
except Exception as e:
|
|
111
106
|
self.logger.error(f"Failed to get or create project entry: {e}")
|
|
@@ -129,7 +124,7 @@ class ProjectRegistry:
|
|
|
129
124
|
# Search all registry files
|
|
130
125
|
for registry_file in self.registry_dir.glob("*.yaml"):
|
|
131
126
|
try:
|
|
132
|
-
with open(registry_file,
|
|
127
|
+
with open(registry_file, encoding="utf-8") as f:
|
|
133
128
|
data = yaml.safe_load(f) or {}
|
|
134
129
|
|
|
135
130
|
# Check if project_path matches
|
|
@@ -311,6 +306,7 @@ class ProjectRegistry:
|
|
|
311
306
|
capture_output=True,
|
|
312
307
|
text=True,
|
|
313
308
|
timeout=5,
|
|
309
|
+
check=False,
|
|
314
310
|
)
|
|
315
311
|
|
|
316
312
|
if result.returncode == 0:
|
|
@@ -324,6 +320,7 @@ class ProjectRegistry:
|
|
|
324
320
|
capture_output=True,
|
|
325
321
|
text=True,
|
|
326
322
|
timeout=5,
|
|
323
|
+
check=False,
|
|
327
324
|
)
|
|
328
325
|
if result.returncode == 0:
|
|
329
326
|
git_info["branch"] = result.stdout.strip()
|
|
@@ -338,6 +335,7 @@ class ProjectRegistry:
|
|
|
338
335
|
capture_output=True,
|
|
339
336
|
text=True,
|
|
340
337
|
timeout=5,
|
|
338
|
+
check=False,
|
|
341
339
|
)
|
|
342
340
|
if result.returncode == 0:
|
|
343
341
|
git_info["remote_url"] = result.stdout.strip()
|
|
@@ -352,6 +350,7 @@ class ProjectRegistry:
|
|
|
352
350
|
capture_output=True,
|
|
353
351
|
text=True,
|
|
354
352
|
timeout=5,
|
|
353
|
+
check=False,
|
|
355
354
|
)
|
|
356
355
|
if result.returncode == 0:
|
|
357
356
|
git_info["last_commit"] = result.stdout.strip()
|
|
@@ -366,6 +365,7 @@ class ProjectRegistry:
|
|
|
366
365
|
capture_output=True,
|
|
367
366
|
text=True,
|
|
368
367
|
timeout=5,
|
|
368
|
+
check=False,
|
|
369
369
|
)
|
|
370
370
|
if result.returncode == 0:
|
|
371
371
|
git_info["has_uncommitted"] = bool(result.stdout.strip())
|
|
@@ -517,7 +517,7 @@ class ProjectRegistry:
|
|
|
517
517
|
try:
|
|
518
518
|
for registry_file in self.registry_dir.glob("*.yaml"):
|
|
519
519
|
try:
|
|
520
|
-
with open(registry_file,
|
|
520
|
+
with open(registry_file, encoding="utf-8") as f:
|
|
521
521
|
data = yaml.safe_load(f) or {}
|
|
522
522
|
projects.append(data)
|
|
523
523
|
except Exception as e:
|
|
@@ -553,7 +553,7 @@ class ProjectRegistry:
|
|
|
553
553
|
try:
|
|
554
554
|
for registry_file in self.registry_dir.glob("*.yaml"):
|
|
555
555
|
try:
|
|
556
|
-
with open(registry_file,
|
|
556
|
+
with open(registry_file, encoding="utf-8") as f:
|
|
557
557
|
data = yaml.safe_load(f) or {}
|
|
558
558
|
|
|
559
559
|
# Check last accessed time
|