claude-mpm 4.14.8__py3-none-any.whl → 4.15.0__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.
Potentially problematic release.
This version of claude-mpm might be problematic. Click here for more details.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/agent_loader.py +13 -1
- claude_mpm/agents/templates/project_organizer.json +3 -3
- claude_mpm/cli/commands/auto_configure.py +7 -9
- claude_mpm/cli/commands/local_deploy.py +3 -2
- claude_mpm/cli/commands/mpm_init.py +4 -4
- claude_mpm/cli/commands/mpm_init_handler.py +8 -3
- claude_mpm/core/base_service.py +13 -12
- claude_mpm/core/enums.py +124 -12
- claude_mpm/services/agents/auto_config_manager.py +9 -10
- claude_mpm/services/agents/deployment/pipeline/steps/agent_processing_step.py +7 -6
- claude_mpm/services/agents/deployment/pipeline/steps/base_step.py +7 -16
- claude_mpm/services/agents/deployment/pipeline/steps/configuration_step.py +4 -3
- claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +5 -3
- claude_mpm/services/agents/deployment/pipeline/steps/validation_step.py +6 -5
- claude_mpm/services/agents/deployment/refactored_agent_deployment_service.py +6 -4
- claude_mpm/services/agents/registry/modification_tracker.py +5 -2
- claude_mpm/services/command_handler_service.py +11 -5
- claude_mpm/services/core/interfaces/process.py +6 -6
- claude_mpm/services/core/models/__init__.py +0 -2
- claude_mpm/services/core/models/agent_config.py +12 -28
- claude_mpm/services/core/models/process.py +19 -42
- claude_mpm/services/local_ops/__init__.py +3 -3
- claude_mpm/services/local_ops/process_manager.py +12 -12
- claude_mpm/services/local_ops/state_manager.py +6 -5
- claude_mpm/services/local_ops/unified_manager.py +2 -2
- claude_mpm/services/mcp_config_manager.py +7 -2
- claude_mpm/services/mcp_gateway/core/__init__.py +1 -2
- claude_mpm/services/mcp_gateway/core/base.py +18 -31
- claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +71 -24
- claude_mpm/services/mcp_gateway/tools/health_check_tool.py +23 -22
- claude_mpm/services/unified/analyzer_strategies/code_analyzer.py +2 -2
- claude_mpm/services/unified/analyzer_strategies/dependency_analyzer.py +1 -1
- claude_mpm/services/unified/analyzer_strategies/performance_analyzer.py +3 -3
- claude_mpm/services/unified/analyzer_strategies/security_analyzer.py +3 -3
- claude_mpm/services/unified/analyzer_strategies/structure_analyzer.py +1 -1
- claude_mpm/services/unified/deployment_strategies/local.py +3 -3
- claude_mpm/services/unified/deployment_strategies/utils.py +5 -5
- claude_mpm/services/unified/deployment_strategies/vercel.py +6 -6
- claude_mpm/services/unified/unified_analyzer.py +8 -5
- claude_mpm/services/unified/unified_deployment.py +2 -2
- {claude_mpm-4.14.8.dist-info → claude_mpm-4.15.0.dist-info}/METADATA +1 -1
- {claude_mpm-4.14.8.dist-info → claude_mpm-4.15.0.dist-info}/RECORD +47 -47
- {claude_mpm-4.14.8.dist-info → claude_mpm-4.15.0.dist-info}/WHEEL +0 -0
- {claude_mpm-4.14.8.dist-info → claude_mpm-4.15.0.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.14.8.dist-info → claude_mpm-4.15.0.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.14.8.dist-info → claude_mpm-4.15.0.dist-info}/top_level.txt +0 -0
|
@@ -2,26 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
from abc import ABC, abstractmethod
|
|
4
4
|
from dataclasses import dataclass
|
|
5
|
-
from enum import Enum
|
|
6
5
|
from typing import Optional
|
|
7
6
|
|
|
7
|
+
from claude_mpm.core.enums import OperationResult
|
|
8
8
|
from claude_mpm.core.logger import get_logger
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
class StepStatus(Enum):
|
|
12
|
-
"""Status of a pipeline step execution."""
|
|
13
|
-
|
|
14
|
-
SUCCESS = "success"
|
|
15
|
-
FAILURE = "failure"
|
|
16
|
-
SKIPPED = "skipped"
|
|
17
|
-
WARNING = "warning"
|
|
18
|
-
|
|
19
|
-
|
|
20
11
|
@dataclass
|
|
21
12
|
class StepResult:
|
|
22
13
|
"""Result of executing a pipeline step."""
|
|
23
14
|
|
|
24
|
-
status:
|
|
15
|
+
status: OperationResult
|
|
25
16
|
message: Optional[str] = None
|
|
26
17
|
error: Optional[Exception] = None
|
|
27
18
|
execution_time: Optional[float] = None
|
|
@@ -29,22 +20,22 @@ class StepResult:
|
|
|
29
20
|
@property
|
|
30
21
|
def is_success(self) -> bool:
|
|
31
22
|
"""Check if the step was successful."""
|
|
32
|
-
return self.status ==
|
|
23
|
+
return self.status == OperationResult.SUCCESS
|
|
33
24
|
|
|
34
25
|
@property
|
|
35
26
|
def is_failure(self) -> bool:
|
|
36
27
|
"""Check if the step failed."""
|
|
37
|
-
return self.status ==
|
|
28
|
+
return self.status == OperationResult.FAILED
|
|
38
29
|
|
|
39
30
|
@property
|
|
40
31
|
def is_skipped(self) -> bool:
|
|
41
32
|
"""Check if the step was skipped."""
|
|
42
|
-
return self.status ==
|
|
33
|
+
return self.status == OperationResult.SKIPPED
|
|
43
34
|
|
|
44
35
|
@property
|
|
45
36
|
def is_warning(self) -> bool:
|
|
46
37
|
"""Check if the step completed with warnings."""
|
|
47
|
-
return self.status ==
|
|
38
|
+
return self.status == OperationResult.WARNING
|
|
48
39
|
|
|
49
40
|
|
|
50
41
|
class BaseDeploymentStep(ABC):
|
|
@@ -67,7 +58,7 @@ class BaseDeploymentStep(ABC):
|
|
|
67
58
|
self.logger = get_logger(f"{__name__}.{self.__class__.__name__}")
|
|
68
59
|
|
|
69
60
|
@abstractmethod
|
|
70
|
-
def execute(self, context) -> StepResult:
|
|
61
|
+
def execute(self, context) -> "StepResult":
|
|
71
62
|
"""Execute this deployment step.
|
|
72
63
|
|
|
73
64
|
Args:
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
import time
|
|
4
4
|
|
|
5
5
|
from claude_mpm.core.config import Config
|
|
6
|
+
from claude_mpm.core.enums import OperationResult
|
|
6
7
|
|
|
7
|
-
from .base_step import BaseDeploymentStep, StepResult
|
|
8
|
+
from .base_step import BaseDeploymentStep, StepResult
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
class ConfigurationLoadStep(BaseDeploymentStep):
|
|
@@ -54,7 +55,7 @@ class ConfigurationLoadStep(BaseDeploymentStep):
|
|
|
54
55
|
context.step_timings[self.name] = execution_time
|
|
55
56
|
|
|
56
57
|
return StepResult(
|
|
57
|
-
status=
|
|
58
|
+
status=OperationResult.SUCCESS,
|
|
58
59
|
message=f"Configuration loaded successfully in {execution_time:.3f}s",
|
|
59
60
|
execution_time=execution_time,
|
|
60
61
|
)
|
|
@@ -68,7 +69,7 @@ class ConfigurationLoadStep(BaseDeploymentStep):
|
|
|
68
69
|
context.add_error(error_msg)
|
|
69
70
|
|
|
70
71
|
return StepResult(
|
|
71
|
-
status=
|
|
72
|
+
status=OperationResult.FAILED,
|
|
72
73
|
message=error_msg,
|
|
73
74
|
error=e,
|
|
74
75
|
execution_time=execution_time,
|
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
import time
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
|
|
6
|
-
from .
|
|
6
|
+
from claude_mpm.core.enums import OperationResult
|
|
7
|
+
|
|
8
|
+
from .base_step import BaseDeploymentStep, StepResult
|
|
7
9
|
|
|
8
10
|
|
|
9
11
|
class TargetDirectorySetupStep(BaseDeploymentStep):
|
|
@@ -59,7 +61,7 @@ class TargetDirectorySetupStep(BaseDeploymentStep):
|
|
|
59
61
|
context.step_timings[self.name] = execution_time
|
|
60
62
|
|
|
61
63
|
return StepResult(
|
|
62
|
-
status=
|
|
64
|
+
status=OperationResult.SUCCESS,
|
|
63
65
|
message=f"Target directory set up at {context.actual_target_dir} in {execution_time:.3f}s",
|
|
64
66
|
execution_time=execution_time,
|
|
65
67
|
)
|
|
@@ -73,7 +75,7 @@ class TargetDirectorySetupStep(BaseDeploymentStep):
|
|
|
73
75
|
context.add_error(error_msg)
|
|
74
76
|
|
|
75
77
|
return StepResult(
|
|
76
|
-
status=
|
|
78
|
+
status=OperationResult.FAILED,
|
|
77
79
|
message=error_msg,
|
|
78
80
|
error=e,
|
|
79
81
|
execution_time=execution_time,
|
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
import time
|
|
4
4
|
|
|
5
|
+
from claude_mpm.core.enums import OperationResult
|
|
5
6
|
from claude_mpm.services.agents.deployment.validation import DeploymentValidator
|
|
6
7
|
|
|
7
|
-
from .base_step import BaseDeploymentStep, StepResult
|
|
8
|
+
from .base_step import BaseDeploymentStep, StepResult
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
class ValidationStep(BaseDeploymentStep):
|
|
@@ -49,13 +50,13 @@ class ValidationStep(BaseDeploymentStep):
|
|
|
49
50
|
|
|
50
51
|
# Determine step status
|
|
51
52
|
if not validation_result.is_valid:
|
|
52
|
-
status =
|
|
53
|
+
status = OperationResult.FAILED
|
|
53
54
|
message = f"Validation failed with {validation_result.error_count} errors in {execution_time:.3f}s"
|
|
54
55
|
elif validation_result.has_warnings:
|
|
55
|
-
status =
|
|
56
|
+
status = OperationResult.WARNING
|
|
56
57
|
message = f"Validation completed with {validation_result.warning_count} warnings in {execution_time:.3f}s"
|
|
57
58
|
else:
|
|
58
|
-
status =
|
|
59
|
+
status = OperationResult.SUCCESS
|
|
59
60
|
message = f"Validation passed successfully in {execution_time:.3f}s"
|
|
60
61
|
|
|
61
62
|
self.logger.info(message)
|
|
@@ -73,7 +74,7 @@ class ValidationStep(BaseDeploymentStep):
|
|
|
73
74
|
context.add_error(error_msg)
|
|
74
75
|
|
|
75
76
|
return StepResult(
|
|
76
|
-
status=
|
|
77
|
+
status=OperationResult.FAILED,
|
|
77
78
|
message=error_msg,
|
|
78
79
|
error=e,
|
|
79
80
|
execution_time=execution_time,
|
|
@@ -117,8 +117,10 @@ class RefactoredAgentDeploymentService(AgentDeploymentInterface):
|
|
|
117
117
|
)
|
|
118
118
|
|
|
119
119
|
# Ensure success field is present
|
|
120
|
-
if
|
|
121
|
-
results[
|
|
120
|
+
if OperationResult.SUCCESS.value not in results:
|
|
121
|
+
results[OperationResult.SUCCESS.value] = not bool(
|
|
122
|
+
results.get("errors", [])
|
|
123
|
+
)
|
|
122
124
|
|
|
123
125
|
self.logger.info(f"Deployment completed: {results.get('success', False)}")
|
|
124
126
|
return results
|
|
@@ -126,7 +128,7 @@ class RefactoredAgentDeploymentService(AgentDeploymentInterface):
|
|
|
126
128
|
except Exception as e:
|
|
127
129
|
self.logger.error(f"Deployment failed: {e}", exc_info=True)
|
|
128
130
|
return {
|
|
129
|
-
|
|
131
|
+
OperationResult.SUCCESS.value: False,
|
|
130
132
|
"error": str(e),
|
|
131
133
|
"deployed": [],
|
|
132
134
|
"updated": [],
|
|
@@ -302,7 +304,7 @@ class RefactoredAgentDeploymentService(AgentDeploymentInterface):
|
|
|
302
304
|
except Exception as e:
|
|
303
305
|
self.logger.error(f"Async deployment failed: {e}", exc_info=True)
|
|
304
306
|
return {
|
|
305
|
-
|
|
307
|
+
OperationResult.SUCCESS.value: False,
|
|
306
308
|
"error": str(e),
|
|
307
309
|
"deployed": [],
|
|
308
310
|
"updated": [],
|
|
@@ -36,6 +36,7 @@ from watchdog.events import FileSystemEvent, FileSystemEventHandler
|
|
|
36
36
|
from watchdog.observers import Observer
|
|
37
37
|
|
|
38
38
|
from claude_mpm.core.base_service import BaseService
|
|
39
|
+
from claude_mpm.core.enums import OperationResult
|
|
39
40
|
from claude_mpm.core.logging_utils import get_logger
|
|
40
41
|
from claude_mpm.core.unified_agent_registry import UnifiedAgentRegistry as AgentRegistry
|
|
41
42
|
from claude_mpm.core.unified_paths import get_path_manager
|
|
@@ -551,12 +552,14 @@ class AgentModificationTracker(BaseService):
|
|
|
551
552
|
errors.append(f"File does not exist: {modification.file_path}")
|
|
552
553
|
|
|
553
554
|
# Update validation status
|
|
554
|
-
modification.validation_status =
|
|
555
|
+
modification.validation_status = (
|
|
556
|
+
OperationResult.FAILED if errors else OperationResult.SUCCESS
|
|
557
|
+
)
|
|
555
558
|
modification.validation_errors = errors
|
|
556
559
|
|
|
557
560
|
except Exception as e:
|
|
558
561
|
self.logger.error(f"Validation error: {e}")
|
|
559
|
-
modification.validation_status =
|
|
562
|
+
modification.validation_status = OperationResult.ERROR
|
|
560
563
|
modification.validation_errors.append(str(e))
|
|
561
564
|
|
|
562
565
|
async def _invalidate_agent_cache(self, agent_name: str) -> None:
|
|
@@ -12,6 +12,7 @@ Extracted from ClaudeRunner to follow Single Responsibility Principle.
|
|
|
12
12
|
from typing import Any, Dict, List
|
|
13
13
|
|
|
14
14
|
from claude_mpm.core.base_service import BaseService
|
|
15
|
+
from claude_mpm.core.enums import OperationResult
|
|
15
16
|
from claude_mpm.services.core.interfaces import CommandHandlerInterface
|
|
16
17
|
|
|
17
18
|
|
|
@@ -169,7 +170,7 @@ class CommandHandlerService(BaseService, CommandHandlerInterface):
|
|
|
169
170
|
if command == "test":
|
|
170
171
|
success = self._handle_test_command(args)
|
|
171
172
|
return {
|
|
172
|
-
|
|
173
|
+
OperationResult.SUCCESS.value: success,
|
|
173
174
|
"command": command,
|
|
174
175
|
"args": args,
|
|
175
176
|
"message": (
|
|
@@ -179,7 +180,7 @@ class CommandHandlerService(BaseService, CommandHandlerInterface):
|
|
|
179
180
|
if command == "agents":
|
|
180
181
|
success = self._handle_agents_command(args)
|
|
181
182
|
return {
|
|
182
|
-
|
|
183
|
+
OperationResult.SUCCESS.value: success,
|
|
183
184
|
"command": command,
|
|
184
185
|
"args": args,
|
|
185
186
|
"message": (
|
|
@@ -189,14 +190,19 @@ class CommandHandlerService(BaseService, CommandHandlerInterface):
|
|
|
189
190
|
),
|
|
190
191
|
}
|
|
191
192
|
return {
|
|
192
|
-
|
|
193
|
+
OperationResult.SUCCESS.value: False,
|
|
193
194
|
"command": command,
|
|
194
195
|
"args": args,
|
|
195
|
-
|
|
196
|
+
OperationResult.ERROR.value: f"Unknown command: {command}",
|
|
196
197
|
"available_commands": self.get_available_commands(),
|
|
197
198
|
}
|
|
198
199
|
except Exception as e:
|
|
199
|
-
return {
|
|
200
|
+
return {
|
|
201
|
+
OperationResult.SUCCESS.value: False,
|
|
202
|
+
"command": command,
|
|
203
|
+
"args": args,
|
|
204
|
+
OperationResult.ERROR.value: str(e),
|
|
205
|
+
}
|
|
200
206
|
|
|
201
207
|
def get_command_help(self, command: str) -> str:
|
|
202
208
|
"""Get help text for a specific command.
|
|
@@ -32,10 +32,10 @@ USAGE:
|
|
|
32
32
|
from abc import ABC, abstractmethod
|
|
33
33
|
from typing import Dict, List, Optional
|
|
34
34
|
|
|
35
|
+
from claude_mpm.core.enums import ServiceState
|
|
35
36
|
from claude_mpm.services.core.models.process import (
|
|
36
37
|
DeploymentState,
|
|
37
38
|
ProcessInfo,
|
|
38
|
-
ProcessStatus,
|
|
39
39
|
StartConfig,
|
|
40
40
|
)
|
|
41
41
|
|
|
@@ -102,12 +102,12 @@ class IDeploymentStateManager(ABC):
|
|
|
102
102
|
"""
|
|
103
103
|
|
|
104
104
|
@abstractmethod
|
|
105
|
-
def get_deployments_by_status(self, status:
|
|
105
|
+
def get_deployments_by_status(self, status: ServiceState) -> List[DeploymentState]:
|
|
106
106
|
"""
|
|
107
107
|
Get all deployments with a specific status.
|
|
108
108
|
|
|
109
109
|
Args:
|
|
110
|
-
status:
|
|
110
|
+
status: ServiceState to filter by
|
|
111
111
|
|
|
112
112
|
Returns:
|
|
113
113
|
List of matching DeploymentState objects
|
|
@@ -168,14 +168,14 @@ class IDeploymentStateManager(ABC):
|
|
|
168
168
|
|
|
169
169
|
@abstractmethod
|
|
170
170
|
def update_deployment_status(
|
|
171
|
-
self, deployment_id: str, status:
|
|
171
|
+
self, deployment_id: str, status: ServiceState
|
|
172
172
|
) -> bool:
|
|
173
173
|
"""
|
|
174
174
|
Update the status of a deployment.
|
|
175
175
|
|
|
176
176
|
Args:
|
|
177
177
|
deployment_id: Unique deployment identifier
|
|
178
|
-
status: New
|
|
178
|
+
status: New ServiceState
|
|
179
179
|
|
|
180
180
|
Returns:
|
|
181
181
|
True if updated, False if deployment not found
|
|
@@ -290,7 +290,7 @@ class ILocalProcessManager(ABC):
|
|
|
290
290
|
|
|
291
291
|
@abstractmethod
|
|
292
292
|
def list_processes(
|
|
293
|
-
self, status_filter: Optional[
|
|
293
|
+
self, status_filter: Optional[ServiceState] = None
|
|
294
294
|
) -> List[ProcessInfo]:
|
|
295
295
|
"""
|
|
296
296
|
List all managed processes.
|
|
@@ -24,7 +24,6 @@ from .process import (
|
|
|
24
24
|
PROTECTED_PORT_RANGES,
|
|
25
25
|
DeploymentState,
|
|
26
26
|
ProcessInfo,
|
|
27
|
-
ProcessStatus,
|
|
28
27
|
StartConfig,
|
|
29
28
|
is_port_protected,
|
|
30
29
|
)
|
|
@@ -63,7 +62,6 @@ __all__ = [ # noqa: RUF022 - Grouped by category with comments for clarity
|
|
|
63
62
|
"ValidationResult",
|
|
64
63
|
"ConfigurationPreview",
|
|
65
64
|
# Process management models
|
|
66
|
-
"ProcessStatus",
|
|
67
65
|
"DeploymentState",
|
|
68
66
|
"ProcessInfo",
|
|
69
67
|
"StartConfig",
|
|
@@ -17,6 +17,8 @@ from dataclasses import dataclass, field
|
|
|
17
17
|
from enum import Enum
|
|
18
18
|
from typing import Any, Dict, List, Optional
|
|
19
19
|
|
|
20
|
+
from ....core.enums import OperationResult, ValidationSeverity
|
|
21
|
+
|
|
20
22
|
|
|
21
23
|
class AgentSpecialization(str, Enum):
|
|
22
24
|
"""Agent specialization categories.
|
|
@@ -154,20 +156,6 @@ class AgentRecommendation:
|
|
|
154
156
|
}
|
|
155
157
|
|
|
156
158
|
|
|
157
|
-
class ConfigurationStatus(str, Enum):
|
|
158
|
-
"""Status of configuration operation.
|
|
159
|
-
|
|
160
|
-
WHY: Configuration can succeed, fail, or partially succeed. This enum
|
|
161
|
-
provides a standardized way to communicate operation outcomes.
|
|
162
|
-
"""
|
|
163
|
-
|
|
164
|
-
SUCCESS = "success"
|
|
165
|
-
PARTIAL_SUCCESS = "partial_success"
|
|
166
|
-
FAILURE = "failure"
|
|
167
|
-
VALIDATION_ERROR = "validation_error"
|
|
168
|
-
USER_CANCELLED = "user_cancelled"
|
|
169
|
-
|
|
170
|
-
|
|
171
159
|
@dataclass
|
|
172
160
|
class ConfigurationResult:
|
|
173
161
|
"""Result of automated configuration operation.
|
|
@@ -179,9 +167,17 @@ class ConfigurationResult:
|
|
|
179
167
|
DESIGN DECISION: Separates successful and failed deployments to enable
|
|
180
168
|
proper error handling. Includes validation results and user-facing
|
|
181
169
|
messages for transparency.
|
|
170
|
+
|
|
171
|
+
NOTE: Uses core OperationResult enum (consolidated from ConfigurationStatus
|
|
172
|
+
in Phase 3A Batch 25). Mappings:
|
|
173
|
+
- SUCCESS → OperationResult.SUCCESS
|
|
174
|
+
- PARTIAL_SUCCESS → OperationResult.WARNING (partial success with issues)
|
|
175
|
+
- FAILURE → OperationResult.FAILED
|
|
176
|
+
- VALIDATION_ERROR → OperationResult.ERROR
|
|
177
|
+
- USER_CANCELLED → OperationResult.CANCELLED
|
|
182
178
|
"""
|
|
183
179
|
|
|
184
|
-
status:
|
|
180
|
+
status: OperationResult
|
|
185
181
|
deployed_agents: List[str] = field(default_factory=list)
|
|
186
182
|
failed_agents: List[str] = field(default_factory=list)
|
|
187
183
|
validation_warnings: List[str] = field(default_factory=list)
|
|
@@ -193,7 +189,7 @@ class ConfigurationResult:
|
|
|
193
189
|
@property
|
|
194
190
|
def is_successful(self) -> bool:
|
|
195
191
|
"""Check if configuration was completely successful."""
|
|
196
|
-
return self.status ==
|
|
192
|
+
return self.status == OperationResult.SUCCESS
|
|
197
193
|
|
|
198
194
|
@property
|
|
199
195
|
def has_failures(self) -> bool:
|
|
@@ -223,18 +219,6 @@ class ConfigurationResult:
|
|
|
223
219
|
}
|
|
224
220
|
|
|
225
221
|
|
|
226
|
-
class ValidationSeverity(str, Enum):
|
|
227
|
-
"""Severity level for validation issues.
|
|
228
|
-
|
|
229
|
-
WHY: Not all validation issues are equally critical. This enum enables
|
|
230
|
-
categorization of issues by severity to support appropriate handling.
|
|
231
|
-
"""
|
|
232
|
-
|
|
233
|
-
ERROR = "error" # Blocks deployment
|
|
234
|
-
WARNING = "warning" # Should be reviewed but doesn't block
|
|
235
|
-
INFO = "info" # Informational only
|
|
236
|
-
|
|
237
|
-
|
|
238
222
|
@dataclass
|
|
239
223
|
class ValidationIssue:
|
|
240
224
|
"""Represents a validation issue.
|
|
@@ -3,56 +3,27 @@ Process Management Data Models for Claude MPM Framework
|
|
|
3
3
|
========================================================
|
|
4
4
|
|
|
5
5
|
WHY: This module defines data structures for process management operations,
|
|
6
|
-
including
|
|
6
|
+
including deployment state and runtime information.
|
|
7
7
|
|
|
8
8
|
DESIGN DECISION: Uses dataclasses for immutability and type safety. Provides
|
|
9
|
-
serialization methods for state persistence.
|
|
9
|
+
serialization methods for state persistence. Process status uses ServiceState
|
|
10
|
+
enum from core.enums (consolidated in Phase 3A Batch 24).
|
|
10
11
|
|
|
11
12
|
ARCHITECTURE:
|
|
12
|
-
- ProcessStatus: Enum of process lifecycle states
|
|
13
13
|
- DeploymentState: Complete deployment information for persistence
|
|
14
14
|
- ProcessInfo: Runtime process information
|
|
15
15
|
- StartConfig: Configuration for spawning new processes
|
|
16
|
+
|
|
17
|
+
Note: ProcessStatus enum has been consolidated into ServiceState (core.enums).
|
|
16
18
|
"""
|
|
17
19
|
|
|
18
20
|
import json
|
|
19
21
|
from dataclasses import asdict, dataclass, field
|
|
20
22
|
from datetime import datetime
|
|
21
|
-
from enum import Enum
|
|
22
23
|
from pathlib import Path
|
|
23
24
|
from typing import Any, Dict, List, Optional
|
|
24
25
|
|
|
25
|
-
|
|
26
|
-
class ProcessStatus(Enum):
|
|
27
|
-
"""
|
|
28
|
-
Process lifecycle status.
|
|
29
|
-
|
|
30
|
-
WHY: Explicit status tracking enables proper state machine management
|
|
31
|
-
and prevents invalid state transitions.
|
|
32
|
-
|
|
33
|
-
States:
|
|
34
|
-
STARTING: Process is being spawned
|
|
35
|
-
RUNNING: Process is actively running
|
|
36
|
-
STOPPING: Process is shutting down
|
|
37
|
-
STOPPED: Process has stopped cleanly
|
|
38
|
-
CRASHED: Process terminated unexpectedly
|
|
39
|
-
UNKNOWN: Process state cannot be determined
|
|
40
|
-
"""
|
|
41
|
-
|
|
42
|
-
STARTING = "starting"
|
|
43
|
-
RUNNING = "running"
|
|
44
|
-
STOPPING = "stopping"
|
|
45
|
-
STOPPED = "stopped"
|
|
46
|
-
CRASHED = "crashed"
|
|
47
|
-
UNKNOWN = "unknown"
|
|
48
|
-
|
|
49
|
-
def is_active(self) -> bool:
|
|
50
|
-
"""Check if status represents an active process."""
|
|
51
|
-
return self in (ProcessStatus.STARTING, ProcessStatus.RUNNING)
|
|
52
|
-
|
|
53
|
-
def is_terminal(self) -> bool:
|
|
54
|
-
"""Check if status represents a terminal state."""
|
|
55
|
-
return self in (ProcessStatus.STOPPED, ProcessStatus.CRASHED)
|
|
26
|
+
from claude_mpm.core.enums import ServiceState
|
|
56
27
|
|
|
57
28
|
|
|
58
29
|
@dataclass
|
|
@@ -71,7 +42,7 @@ class DeploymentState:
|
|
|
71
42
|
environment: Environment variables (beyond inherited ones)
|
|
72
43
|
port: Primary port used by the process (if applicable)
|
|
73
44
|
started_at: Timestamp when process was started
|
|
74
|
-
status: Current
|
|
45
|
+
status: Current ServiceState (process lifecycle state)
|
|
75
46
|
metadata: Additional deployment-specific information
|
|
76
47
|
"""
|
|
77
48
|
|
|
@@ -82,7 +53,7 @@ class DeploymentState:
|
|
|
82
53
|
environment: Dict[str, str] = field(default_factory=dict)
|
|
83
54
|
port: Optional[int] = None
|
|
84
55
|
started_at: datetime = field(default_factory=datetime.now)
|
|
85
|
-
status:
|
|
56
|
+
status: ServiceState = ServiceState.STARTING
|
|
86
57
|
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
87
58
|
|
|
88
59
|
def to_dict(self) -> Dict[str, Any]:
|
|
@@ -114,7 +85,14 @@ class DeploymentState:
|
|
|
114
85
|
|
|
115
86
|
# Convert status string to enum
|
|
116
87
|
if isinstance(data.get("status"), str):
|
|
117
|
-
|
|
88
|
+
status_value = data["status"]
|
|
89
|
+
# Handle legacy "crashed" status -> map to ERROR
|
|
90
|
+
if status_value == "crashed":
|
|
91
|
+
data["status"] = (
|
|
92
|
+
ServiceState.ERROR
|
|
93
|
+
) # CRASHED semantically maps to ERROR state
|
|
94
|
+
else:
|
|
95
|
+
data["status"] = ServiceState(status_value)
|
|
118
96
|
|
|
119
97
|
return cls(**data)
|
|
120
98
|
|
|
@@ -153,18 +131,18 @@ class ProcessInfo:
|
|
|
153
131
|
Attributes:
|
|
154
132
|
deployment_id: Unique deployment identifier
|
|
155
133
|
process_id: OS process ID
|
|
156
|
-
status: Current
|
|
134
|
+
status: Current ServiceState (process lifecycle state)
|
|
157
135
|
port: Port the process is using
|
|
158
136
|
uptime_seconds: How long the process has been running
|
|
159
137
|
memory_mb: Current memory usage in megabytes
|
|
160
138
|
cpu_percent: Current CPU usage percentage
|
|
161
139
|
is_responding: Whether the process responds to health checks
|
|
162
|
-
error_message: Error message if status is
|
|
140
|
+
error_message: Error message if status is ERROR (crashed)
|
|
163
141
|
"""
|
|
164
142
|
|
|
165
143
|
deployment_id: str
|
|
166
144
|
process_id: int
|
|
167
|
-
status:
|
|
145
|
+
status: ServiceState
|
|
168
146
|
port: Optional[int] = None
|
|
169
147
|
uptime_seconds: float = 0.0
|
|
170
148
|
memory_mb: float = 0.0
|
|
@@ -252,7 +230,6 @@ __all__ = [
|
|
|
252
230
|
"PROTECTED_PORT_RANGES",
|
|
253
231
|
"DeploymentState",
|
|
254
232
|
"ProcessInfo",
|
|
255
|
-
"ProcessStatus",
|
|
256
233
|
"StartConfig",
|
|
257
234
|
"is_port_protected",
|
|
258
235
|
]
|
|
@@ -32,9 +32,9 @@ USAGE:
|
|
|
32
32
|
DeploymentStateManager,
|
|
33
33
|
HealthCheckManager,
|
|
34
34
|
StartConfig,
|
|
35
|
-
ProcessStatus,
|
|
36
35
|
HealthStatus,
|
|
37
36
|
)
|
|
37
|
+
from claude_mpm.core.enums import ServiceState
|
|
38
38
|
|
|
39
39
|
# Initialize managers
|
|
40
40
|
state_manager = DeploymentStateManager(".claude-mpm/deployment-state.json")
|
|
@@ -54,6 +54,8 @@ USAGE:
|
|
|
54
54
|
|
|
55
55
|
# Start background monitoring
|
|
56
56
|
health_manager.start_monitoring()
|
|
57
|
+
|
|
58
|
+
Note: ProcessStatus has been consolidated into ServiceState (core.enums) as of Phase 3A Batch 24.
|
|
57
59
|
"""
|
|
58
60
|
|
|
59
61
|
# Re-export data models and interfaces for convenience
|
|
@@ -84,7 +86,6 @@ from claude_mpm.services.core.models.process import (
|
|
|
84
86
|
PROTECTED_PORT_RANGES,
|
|
85
87
|
DeploymentState,
|
|
86
88
|
ProcessInfo,
|
|
87
|
-
ProcessStatus,
|
|
88
89
|
StartConfig,
|
|
89
90
|
is_port_protected,
|
|
90
91
|
)
|
|
@@ -147,7 +148,6 @@ __all__ = [
|
|
|
147
148
|
"ProcessInfo",
|
|
148
149
|
"ProcessSpawnError",
|
|
149
150
|
# Data models - Process
|
|
150
|
-
"ProcessStatus",
|
|
151
151
|
"ResourceMonitor",
|
|
152
152
|
"ResourceUsage",
|
|
153
153
|
"RestartAttempt",
|
|
@@ -42,6 +42,7 @@ from typing import List, Optional
|
|
|
42
42
|
|
|
43
43
|
import psutil
|
|
44
44
|
|
|
45
|
+
from claude_mpm.core.enums import ServiceState
|
|
45
46
|
from claude_mpm.services.core.base import SyncBaseService
|
|
46
47
|
from claude_mpm.services.core.interfaces.process import (
|
|
47
48
|
IDeploymentStateManager,
|
|
@@ -50,7 +51,6 @@ from claude_mpm.services.core.interfaces.process import (
|
|
|
50
51
|
from claude_mpm.services.core.models.process import (
|
|
51
52
|
DeploymentState,
|
|
52
53
|
ProcessInfo,
|
|
53
|
-
ProcessStatus,
|
|
54
54
|
StartConfig,
|
|
55
55
|
is_port_protected,
|
|
56
56
|
)
|
|
@@ -203,7 +203,7 @@ class LocalProcessManager(SyncBaseService, ILocalProcessManager):
|
|
|
203
203
|
environment=config.environment,
|
|
204
204
|
port=allocated_port,
|
|
205
205
|
started_at=datetime.now(tz=timezone.utc),
|
|
206
|
-
status=
|
|
206
|
+
status=ServiceState.RUNNING,
|
|
207
207
|
metadata=config.metadata,
|
|
208
208
|
)
|
|
209
209
|
|
|
@@ -248,13 +248,13 @@ class LocalProcessManager(SyncBaseService, ILocalProcessManager):
|
|
|
248
248
|
# Process already dead, just update state
|
|
249
249
|
self.log_info(f"Process {deployment.process_id} already dead")
|
|
250
250
|
self.state_manager.update_deployment_status(
|
|
251
|
-
deployment_id,
|
|
251
|
+
deployment_id, ServiceState.STOPPED
|
|
252
252
|
)
|
|
253
253
|
return True
|
|
254
254
|
|
|
255
255
|
self.log_info(f"Stopping process {deployment.process_id} for {deployment_id}")
|
|
256
256
|
self.state_manager.update_deployment_status(
|
|
257
|
-
deployment_id,
|
|
257
|
+
deployment_id, ServiceState.STOPPING
|
|
258
258
|
)
|
|
259
259
|
|
|
260
260
|
try:
|
|
@@ -262,7 +262,7 @@ class LocalProcessManager(SyncBaseService, ILocalProcessManager):
|
|
|
262
262
|
# Force kill immediately
|
|
263
263
|
self._kill_process_group(process)
|
|
264
264
|
self.state_manager.update_deployment_status(
|
|
265
|
-
deployment_id,
|
|
265
|
+
deployment_id, ServiceState.STOPPED
|
|
266
266
|
)
|
|
267
267
|
return True
|
|
268
268
|
|
|
@@ -275,7 +275,7 @@ class LocalProcessManager(SyncBaseService, ILocalProcessManager):
|
|
|
275
275
|
if not process.is_running():
|
|
276
276
|
self.log_info(f"Process {deployment.process_id} stopped gracefully")
|
|
277
277
|
self.state_manager.update_deployment_status(
|
|
278
|
-
deployment_id,
|
|
278
|
+
deployment_id, ServiceState.STOPPED
|
|
279
279
|
)
|
|
280
280
|
return True
|
|
281
281
|
time.sleep(0.1)
|
|
@@ -286,14 +286,14 @@ class LocalProcessManager(SyncBaseService, ILocalProcessManager):
|
|
|
286
286
|
)
|
|
287
287
|
self._kill_process_group(process)
|
|
288
288
|
self.state_manager.update_deployment_status(
|
|
289
|
-
deployment_id,
|
|
289
|
+
deployment_id, ServiceState.STOPPED
|
|
290
290
|
)
|
|
291
291
|
return True
|
|
292
292
|
|
|
293
293
|
except psutil.NoSuchProcess:
|
|
294
294
|
# Process died during shutdown
|
|
295
295
|
self.state_manager.update_deployment_status(
|
|
296
|
-
deployment_id,
|
|
296
|
+
deployment_id, ServiceState.STOPPED
|
|
297
297
|
)
|
|
298
298
|
return True
|
|
299
299
|
|
|
@@ -370,9 +370,9 @@ class LocalProcessManager(SyncBaseService, ILocalProcessManager):
|
|
|
370
370
|
|
|
371
371
|
# Determine status
|
|
372
372
|
if process.is_running():
|
|
373
|
-
status =
|
|
373
|
+
status = ServiceState.RUNNING
|
|
374
374
|
else:
|
|
375
|
-
status =
|
|
375
|
+
status = ServiceState.STOPPED
|
|
376
376
|
|
|
377
377
|
return ProcessInfo(
|
|
378
378
|
deployment_id=deployment_id,
|
|
@@ -389,13 +389,13 @@ class LocalProcessManager(SyncBaseService, ILocalProcessManager):
|
|
|
389
389
|
return ProcessInfo(
|
|
390
390
|
deployment_id=deployment_id,
|
|
391
391
|
process_id=deployment.process_id,
|
|
392
|
-
status=
|
|
392
|
+
status=ServiceState.ERROR, # CRASHED semantically maps to ERROR state
|
|
393
393
|
port=deployment.port,
|
|
394
394
|
error_message="Process no longer exists",
|
|
395
395
|
)
|
|
396
396
|
|
|
397
397
|
def list_processes(
|
|
398
|
-
self, status_filter: Optional[
|
|
398
|
+
self, status_filter: Optional[ServiceState] = None
|
|
399
399
|
) -> List[ProcessInfo]:
|
|
400
400
|
"""
|
|
401
401
|
List all managed processes.
|