claude-mpm 4.6.1__py3-none-any.whl → 4.7.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.
Files changed (111) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/BASE_ENGINEER.md +206 -48
  3. claude_mpm/agents/BASE_PROMPT_ENGINEER.md +787 -0
  4. claude_mpm/agents/base_agent_loader.py +3 -1
  5. claude_mpm/agents/templates/engineer.json +10 -4
  6. claude_mpm/agents/templates/prompt-engineer.json +517 -87
  7. claude_mpm/cli/commands/cleanup.py +1 -1
  8. claude_mpm/cli/commands/mcp_setup_external.py +2 -2
  9. claude_mpm/cli/commands/memory.py +1 -1
  10. claude_mpm/cli/commands/mpm_init.py +5 -4
  11. claude_mpm/cli/commands/run.py +4 -4
  12. claude_mpm/cli/shared/argument_patterns.py +18 -11
  13. claude_mpm/cli/shared/base_command.py +1 -1
  14. claude_mpm/config/experimental_features.py +3 -3
  15. claude_mpm/config/socketio_config.py +1 -1
  16. claude_mpm/core/cache.py +2 -2
  17. claude_mpm/core/claude_runner.py +5 -7
  18. claude_mpm/core/container.py +10 -4
  19. claude_mpm/core/file_utils.py +10 -8
  20. claude_mpm/core/framework/formatters/context_generator.py +3 -2
  21. claude_mpm/core/framework/loaders/agent_loader.py +11 -7
  22. claude_mpm/core/injectable_service.py +11 -8
  23. claude_mpm/core/interactive_session.py +5 -4
  24. claude_mpm/core/oneshot_session.py +3 -2
  25. claude_mpm/core/pm_hook_interceptor.py +15 -9
  26. claude_mpm/core/unified_paths.py +6 -5
  27. claude_mpm/dashboard/api/simple_directory.py +16 -17
  28. claude_mpm/hooks/claude_hooks/event_handlers.py +3 -2
  29. claude_mpm/hooks/claude_hooks/hook_handler_eventbus.py +2 -2
  30. claude_mpm/hooks/claude_hooks/hook_handler_original.py +2 -2
  31. claude_mpm/hooks/claude_hooks/installer.py +10 -10
  32. claude_mpm/hooks/claude_hooks/response_tracking.py +3 -2
  33. claude_mpm/hooks/claude_hooks/services/state_manager.py +3 -2
  34. claude_mpm/hooks/tool_call_interceptor.py +6 -3
  35. claude_mpm/models/agent_session.py +3 -1
  36. claude_mpm/scripts/mcp_server.py +3 -5
  37. claude_mpm/services/agents/agent_builder.py +4 -4
  38. claude_mpm/services/agents/deployment/deployment_type_detector.py +10 -14
  39. claude_mpm/services/agents/deployment/local_template_deployment.py +6 -3
  40. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +15 -11
  41. claude_mpm/services/agents/deployment/system_instructions_deployer.py +9 -6
  42. claude_mpm/services/agents/loading/agent_profile_loader.py +1 -2
  43. claude_mpm/services/agents/memory/agent_memory_manager.py +27 -27
  44. claude_mpm/services/agents/memory/content_manager.py +9 -4
  45. claude_mpm/services/claude_session_logger.py +5 -8
  46. claude_mpm/services/cli/memory_crud_service.py +1 -1
  47. claude_mpm/services/cli/memory_output_formatter.py +1 -1
  48. claude_mpm/services/cli/startup_checker.py +13 -10
  49. claude_mpm/services/cli/unified_dashboard_manager.py +10 -6
  50. claude_mpm/services/command_deployment_service.py +9 -7
  51. claude_mpm/services/core/path_resolver.py +8 -5
  52. claude_mpm/services/diagnostics/checks/agent_check.py +4 -7
  53. claude_mpm/services/diagnostics/checks/installation_check.py +19 -16
  54. claude_mpm/services/diagnostics/checks/mcp_services_check.py +30 -28
  55. claude_mpm/services/diagnostics/checks/startup_log_check.py +5 -3
  56. claude_mpm/services/events/core.py +2 -3
  57. claude_mpm/services/framework_claude_md_generator/content_validator.py +2 -2
  58. claude_mpm/services/hook_installer_service.py +2 -3
  59. claude_mpm/services/hook_service.py +5 -6
  60. claude_mpm/services/mcp_gateway/auto_configure.py +4 -5
  61. claude_mpm/services/mcp_gateway/main.py +7 -4
  62. claude_mpm/services/mcp_gateway/server/stdio_server.py +3 -4
  63. claude_mpm/services/mcp_gateway/tools/document_summarizer.py +1 -2
  64. claude_mpm/services/mcp_service_verifier.py +18 -17
  65. claude_mpm/services/memory/builder.py +1 -2
  66. claude_mpm/services/memory/indexed_memory.py +1 -1
  67. claude_mpm/services/memory/optimizer.py +1 -2
  68. claude_mpm/services/monitor/daemon_manager.py +3 -3
  69. claude_mpm/services/monitor/handlers/file.py +5 -4
  70. claude_mpm/services/monitor/management/lifecycle.py +1 -1
  71. claude_mpm/services/monitor/server.py +14 -12
  72. claude_mpm/services/project/architecture_analyzer.py +5 -5
  73. claude_mpm/services/project/metrics_collector.py +4 -4
  74. claude_mpm/services/project/project_organizer.py +4 -4
  75. claude_mpm/services/project/registry.py +9 -3
  76. claude_mpm/services/shared/config_service_base.py +2 -3
  77. claude_mpm/services/socketio/handlers/file.py +5 -4
  78. claude_mpm/services/socketio/handlers/git.py +7 -7
  79. claude_mpm/services/socketio/server/core.py +10 -10
  80. claude_mpm/services/subprocess_launcher_service.py +5 -10
  81. claude_mpm/services/ticket_services/formatter_service.py +1 -1
  82. claude_mpm/services/ticket_services/validation_service.py +5 -5
  83. claude_mpm/services/unified/analyzer_strategies/dependency_analyzer.py +5 -5
  84. claude_mpm/services/unified/analyzer_strategies/performance_analyzer.py +4 -4
  85. claude_mpm/services/unified/analyzer_strategies/security_analyzer.py +4 -4
  86. claude_mpm/services/unified/analyzer_strategies/structure_analyzer.py +4 -4
  87. claude_mpm/services/unified/config_strategies/error_handling_strategy.py +4 -4
  88. claude_mpm/services/unified/config_strategies/file_loader_strategy.py +6 -2
  89. claude_mpm/services/unified/config_strategies/unified_config_service.py +24 -13
  90. claude_mpm/services/version_control/conflict_resolution.py +6 -2
  91. claude_mpm/services/version_control/git_operations.py +1 -1
  92. claude_mpm/services/version_control/version_parser.py +1 -1
  93. claude_mpm/storage/state_storage.py +3 -3
  94. claude_mpm/tools/__main__.py +1 -1
  95. claude_mpm/tools/code_tree_analyzer.py +17 -14
  96. claude_mpm/tools/socketio_debug.py +7 -7
  97. claude_mpm/utils/common.py +6 -2
  98. claude_mpm/utils/config_manager.py +9 -3
  99. claude_mpm/utils/database_connector.py +4 -4
  100. claude_mpm/utils/dependency_strategies.py +1 -1
  101. claude_mpm/utils/environment_context.py +3 -2
  102. claude_mpm/utils/file_utils.py +1 -2
  103. claude_mpm/utils/path_operations.py +3 -1
  104. claude_mpm/utils/robust_installer.py +3 -4
  105. claude_mpm/validation/frontmatter_validator.py +4 -4
  106. {claude_mpm-4.6.1.dist-info → claude_mpm-4.7.0.dist-info}/METADATA +1 -1
  107. {claude_mpm-4.6.1.dist-info → claude_mpm-4.7.0.dist-info}/RECORD +111 -110
  108. {claude_mpm-4.6.1.dist-info → claude_mpm-4.7.0.dist-info}/WHEEL +0 -0
  109. {claude_mpm-4.6.1.dist-info → claude_mpm-4.7.0.dist-info}/entry_points.txt +0 -0
  110. {claude_mpm-4.6.1.dist-info → claude_mpm-4.7.0.dist-info}/licenses/LICENSE +0 -0
  111. {claude_mpm-4.6.1.dist-info → claude_mpm-4.7.0.dist-info}/top_level.txt +0 -0
@@ -11,7 +11,7 @@ Created: 2025-01-26
11
11
 
12
12
  import fnmatch
13
13
  from pathlib import Path
14
- from typing import Any, Dict, List, Optional, Tuple
14
+ from typing import Any, ClassVar, Dict, List, Optional, Tuple
15
15
 
16
16
  from claude_mpm.core.logging_utils import get_logger
17
17
 
@@ -38,7 +38,7 @@ class StructureAnalyzerStrategy(AnalyzerStrategy):
38
38
  """
39
39
 
40
40
  # Common project patterns
41
- PROJECT_PATTERNS = {
41
+ PROJECT_PATTERNS: ClassVar[dict] = {
42
42
  "mvc": {
43
43
  "dirs": ["models", "views", "controllers"],
44
44
  "confidence": 0.8,
@@ -62,7 +62,7 @@ class StructureAnalyzerStrategy(AnalyzerStrategy):
62
62
  }
63
63
 
64
64
  # Language-specific structure patterns
65
- LANGUAGE_STRUCTURES = {
65
+ LANGUAGE_STRUCTURES: ClassVar[dict] = {
66
66
  "python": {
67
67
  "src_patterns": ["src", "lib", "app"],
68
68
  "test_patterns": ["tests", "test", "spec"],
@@ -90,7 +90,7 @@ class StructureAnalyzerStrategy(AnalyzerStrategy):
90
90
  }
91
91
 
92
92
  # Common ignore patterns
93
- IGNORE_PATTERNS = [
93
+ IGNORE_PATTERNS: ClassVar[list] = [
94
94
  "*.pyc",
95
95
  "__pycache__",
96
96
  ".git",
@@ -10,7 +10,7 @@ from dataclasses import dataclass, field
10
10
  from datetime import datetime
11
11
  from enum import Enum
12
12
  from pathlib import Path
13
- from typing import Any, Callable, Dict, List, Optional, Type, Union
13
+ from typing import Any, Callable, ClassVar, Dict, List, Optional, Type, Union
14
14
 
15
15
  from claude_mpm.core.logging_utils import get_logger
16
16
 
@@ -105,7 +105,7 @@ class BaseErrorHandler(ABC):
105
105
  class FileIOErrorHandler(BaseErrorHandler):
106
106
  """Handles file I/O errors - consolidates 18 file error patterns"""
107
107
 
108
- ERROR_MAPPING = {
108
+ ERROR_MAPPING: ClassVar[dict] = {
109
109
  FileNotFoundError: "File not found",
110
110
  PermissionError: "Permission denied",
111
111
  IsADirectoryError: "Path is a directory",
@@ -241,7 +241,7 @@ class FileIOErrorHandler(BaseErrorHandler):
241
241
  class ParsingErrorHandler(BaseErrorHandler):
242
242
  """Handles parsing errors - consolidates 22 parsing error patterns"""
243
243
 
244
- PARSER_ERRORS = {
244
+ PARSER_ERRORS: ClassVar[dict] = {
245
245
  json.JSONDecodeError: ErrorCategory.PARSING,
246
246
  ValueError: ErrorCategory.PARSING, # Common for parsing
247
247
  SyntaxError: ErrorCategory.PARSING,
@@ -559,7 +559,7 @@ class ValidationErrorHandler(BaseErrorHandler):
559
559
  class NetworkErrorHandler(BaseErrorHandler):
560
560
  """Handles network-related errors - consolidates 12 network error patterns"""
561
561
 
562
- NETWORK_ERRORS = [
562
+ NETWORK_ERRORS: ClassVar[list] = [
563
563
  ConnectionError,
564
564
  TimeoutError,
565
565
  ConnectionRefusedError,
@@ -64,13 +64,17 @@ class BaseFileLoader(ABC):
64
64
  def _read_file(self, path: Path, encoding: str = "utf-8") -> str:
65
65
  """Read file with proper error handling"""
66
66
  try:
67
- with open(path, encoding=encoding) as f:
67
+ with Path(path).open(
68
+ encoding=encoding,
69
+ ) as f:
68
70
  return f.read()
69
71
  except UnicodeDecodeError:
70
72
  # Try with different encodings
71
73
  for enc in ["latin-1", "cp1252", "utf-16"]:
72
74
  try:
73
- with open(path, encoding=enc) as f:
75
+ with Path(path).open(
76
+ encoding=enc,
77
+ ) as f:
74
78
  self.logger.warning(
75
79
  f"Read {path} with fallback encoding: {enc}"
76
80
  )
@@ -254,9 +254,10 @@ class UnifiedConfigService:
254
254
  # Apply specified validators or use schema-defined ones
255
255
  if validators:
256
256
  for validator_name in validators:
257
- if validator_name in self._validators:
258
- if not self._validators[validator_name](config, schema):
259
- return False
257
+ if validator_name in self._validators and not self._validators[
258
+ validator_name
259
+ ](config, schema):
260
+ return False
260
261
  else:
261
262
  # Use schema to determine validators
262
263
  return self._validate_schema(config, schema)
@@ -563,9 +564,12 @@ class UnifiedConfigService:
563
564
  """Validate enum values"""
564
565
  properties = schema.get("properties", {})
565
566
  for key, value in config.items():
566
- if key in properties and "enum" in properties[key]:
567
- if value not in properties[key]["enum"]:
568
- return False
567
+ if (
568
+ key in properties
569
+ and "enum" in properties[key]
570
+ and value not in properties[key]["enum"]
571
+ ):
572
+ return False
569
573
  return True
570
574
 
571
575
  def _validate_schema(self, config: Dict[str, Any], schema: Dict) -> bool:
@@ -596,9 +600,13 @@ class UnifiedConfigService:
596
600
  """Validate unique values in arrays"""
597
601
  properties = schema.get("properties", {})
598
602
  for key, value in config.items():
599
- if key in properties and properties[key].get("uniqueItems"):
600
- if isinstance(value, list) and len(value) != len(set(map(str, value))):
601
- return False
603
+ if (
604
+ key in properties
605
+ and properties[key].get("uniqueItems")
606
+ and isinstance(value, list)
607
+ and len(value) != len(set(map(str, value)))
608
+ ):
609
+ return False
602
610
  return True
603
611
 
604
612
  def _validate_format(self, config: Dict[str, Any], schema: Dict) -> bool:
@@ -641,10 +649,13 @@ class UnifiedConfigService:
641
649
  """Recursively validate nested structures"""
642
650
  properties = schema.get("properties", {})
643
651
  for key, value in config.items():
644
- if key in properties and isinstance(value, dict):
645
- if "properties" in properties[key]:
646
- if not self._validate_schema(value, properties[key]):
647
- return False
652
+ if (
653
+ key in properties
654
+ and isinstance(value, dict)
655
+ and "properties" in properties[key]
656
+ and not self._validate_schema(value, properties[key])
657
+ ):
658
+ return False
648
659
  return True
649
660
 
650
661
  def _validate_cross_field(self, config: Dict[str, Any], schema: Dict) -> bool:
@@ -274,7 +274,9 @@ class ConflictResolutionManager:
274
274
  )
275
275
 
276
276
  try:
277
- with open(full_path, encoding="utf-8") as f:
277
+ with Path(full_path).open(
278
+ encoding="utf-8",
279
+ ) as f:
278
280
  content = f.read()
279
281
 
280
282
  # Parse conflict markers
@@ -587,7 +589,9 @@ class ConflictResolutionManager:
587
589
 
588
590
  try:
589
591
  # Read current content
590
- with open(file_path, encoding="utf-8") as f:
592
+ with Path(file_path).open(
593
+ encoding="utf-8",
594
+ ) as f:
591
595
  content = f.read()
592
596
 
593
597
  # Apply resolution strategy
@@ -333,7 +333,7 @@ class GitOperationsManager:
333
333
  """
334
334
  try:
335
335
  # Convert to relative path if absolute
336
- if os.path.isabs(file_path):
336
+ if Path(file_path).is_absolute():
337
337
  try:
338
338
  file_path = os.path.relpath(file_path, self.project_root)
339
339
  except ValueError:
@@ -113,7 +113,7 @@ class EnhancedVersionParser:
113
113
  r"(\d+)\.(\d+)\.(\d+)(?:-([a-zA-Z0-9\-\.]+))?(?:\+([a-zA-Z0-9\-\.]+))?"
114
114
  )
115
115
  self._changelog_version_pattern = re.compile(
116
- r"##\s*\[?([0-9]+\.[0-9]+\.[0-9]+[^\]]*)\]?\s*[-–]\s*(\d{4}-\d{2}-\d{2})?"
116
+ r"##\s*\[?([0-9]+\.[0-9]+\.[0-9]+[^\]]*)\]?\s*[--]\s*(\d{4}-\d{2}-\d{2})?"
117
117
  )
118
118
 
119
119
  def _get_cached(self, key: str) -> Optional[any]:
@@ -268,7 +268,7 @@ class StateStorage:
268
268
  finally:
269
269
  # Clean up temp file if it still exists
270
270
  if Path(temp_path).exists():
271
- os.unlink(temp_path)
271
+ Path(temp_path).unlink()
272
272
 
273
273
  except Exception as e:
274
274
  self.logger.error(f"Atomic write failed for {file_path}: {e}")
@@ -288,14 +288,14 @@ class StateStorage:
288
288
  """
289
289
  if not self.supports_locking:
290
290
  # No locking on Windows
291
- with open(file_path, mode) as f:
291
+ with Path(file_path).open(mode) as f:
292
292
  yield f
293
293
  return
294
294
 
295
295
  # Unix-like systems with fcntl
296
296
  f = None
297
297
  try:
298
- f = open(file_path, mode)
298
+ f = Path(file_path).open(mode)
299
299
 
300
300
  # Try to acquire lock (non-blocking)
301
301
  max_attempts = 50
@@ -118,7 +118,7 @@ def main():
118
118
  output_file = None
119
119
  if args.output_file:
120
120
  try:
121
- output_file = open(args.output_file, "w")
121
+ output_file = Path(args.output_file).open("w")
122
122
  except Exception as e:
123
123
  emit_json_event(
124
124
  "code:analysis:error", {"message": f"Failed to open output file: {e}"}
@@ -21,7 +21,7 @@ import time
21
21
  from dataclasses import dataclass
22
22
  from datetime import timezone
23
23
  from pathlib import Path
24
- from typing import Any, Dict, List, Optional
24
+ from typing import Any, ClassVar, Dict, List, Optional
25
25
 
26
26
  try:
27
27
  import pathspec
@@ -31,14 +31,13 @@ except ImportError:
31
31
  PATHSPEC_AVAILABLE = False
32
32
  pathspec = None
33
33
 
34
- try:
34
+ import importlib.util
35
+
36
+ if importlib.util.find_spec("tree_sitter"):
35
37
  import tree_sitter
36
- import tree_sitter_javascript
37
- import tree_sitter_python
38
- import tree_sitter_typescript
39
38
 
40
39
  TREE_SITTER_AVAILABLE = True
41
- except ImportError:
40
+ else:
42
41
  TREE_SITTER_AVAILABLE = False
43
42
  tree_sitter = None
44
43
 
@@ -54,7 +53,7 @@ class GitignoreManager:
54
53
  """
55
54
 
56
55
  # Default patterns that should always be ignored
57
- DEFAULT_PATTERNS = [
56
+ DEFAULT_PATTERNS: ClassVar[list] = [
58
57
  ".git/",
59
58
  "__pycache__/",
60
59
  "*.pyc",
@@ -88,13 +87,13 @@ class GitignoreManager:
88
87
  ]
89
88
 
90
89
  # Additional patterns to hide dotfiles (when enabled)
91
- DOTFILE_PATTERNS = [
90
+ DOTFILE_PATTERNS: ClassVar[list] = [
92
91
  ".*", # All dotfiles
93
92
  ".*/", # All dot directories
94
93
  ]
95
94
 
96
95
  # Important files/directories to always show
97
- DOTFILE_EXCEPTIONS = {
96
+ DOTFILE_EXCEPTIONS: ClassVar[set] = {
98
97
  # Removed .gitignore from exceptions - it should be hidden by default
99
98
  ".env.example",
100
99
  ".env.sample",
@@ -260,7 +259,9 @@ class GitignoreManager:
260
259
 
261
260
  patterns = []
262
261
  try:
263
- with open(gitignore_path, encoding="utf-8") as f:
262
+ with Path(gitignore_path).open(
263
+ encoding="utf-8",
264
+ ) as f:
264
265
  for line in f:
265
266
  line = line.strip()
266
267
  # Skip empty lines and comments
@@ -383,7 +384,9 @@ class PythonAnalyzer:
383
384
  nodes = []
384
385
 
385
386
  try:
386
- with open(file_path, encoding="utf-8") as f:
387
+ with Path(file_path).open(
388
+ encoding="utf-8",
389
+ ) as f:
387
390
  source = f.read()
388
391
 
389
392
  tree = ast.parse(source, filename=str(file_path))
@@ -635,7 +638,7 @@ class MultiLanguageAnalyzer:
635
638
  allowing us to support JavaScript, TypeScript, and other languages.
636
639
  """
637
640
 
638
- LANGUAGE_PARSERS = {
641
+ LANGUAGE_PARSERS: ClassVar[dict] = {
639
642
  "python": "tree_sitter_python",
640
643
  "javascript": "tree_sitter_javascript",
641
644
  "typescript": "tree_sitter_typescript",
@@ -837,7 +840,7 @@ class CodeTreeAnalyzer:
837
840
  """
838
841
 
839
842
  # Define code file extensions at class level for directory filtering
840
- CODE_EXTENSIONS = {
843
+ CODE_EXTENSIONS: ClassVar[set] = {
841
844
  ".py",
842
845
  ".js",
843
846
  ".jsx",
@@ -888,7 +891,7 @@ class CodeTreeAnalyzer:
888
891
  }
889
892
 
890
893
  # File extensions to language mapping
891
- LANGUAGE_MAP = {
894
+ LANGUAGE_MAP: ClassVar[dict] = {
892
895
  ".py": "python",
893
896
  ".js": "javascript",
894
897
  ".jsx": "javascript",
@@ -8,6 +8,9 @@ for developers working on the claude-mpm dashboard.
8
8
 
9
9
  import argparse
10
10
  import asyncio
11
+
12
+ # Try to import Rich for enhanced output
13
+ import importlib.util
11
14
  import json
12
15
  import signal
13
16
  import sys
@@ -20,18 +23,15 @@ from typing import Any, Dict, List, Optional, Set
20
23
 
21
24
  import socketio
22
25
 
23
- # Try to import Rich for enhanced output
24
- try:
26
+ if importlib.util.find_spec("rich"):
25
27
  from rich.console import Console
26
- from rich.layout import Layout
27
- from rich.live import Live
28
28
  from rich.panel import Panel
29
29
  from rich.table import Table
30
30
  from rich.text import Text
31
31
 
32
32
  RICH_AVAILABLE = True
33
33
  console = Console()
34
- except ImportError:
34
+ else:
35
35
  RICH_AVAILABLE = False
36
36
  console = None
37
37
 
@@ -266,7 +266,7 @@ class SocketIODebugger:
266
266
  "PostToolUse": "✅",
267
267
  "Error": "❌",
268
268
  "Warning": "⚠️",
269
- "Info": "ℹ️",
269
+ "Info": "[INFO]️",
270
270
  "MemoryUpdate": "🧠",
271
271
  "ConfigChange": "⚙️",
272
272
  }
@@ -463,7 +463,7 @@ class SocketIODebugger:
463
463
  console.print(f"[{timestamp}] {message}", style=style)
464
464
  else:
465
465
  prefixes = {
466
- "info": "ℹ️",
466
+ "info": "[INFO]️",
467
467
  "success": "✅",
468
468
  "warning": "⚠️",
469
469
  "error": "❌",
@@ -46,7 +46,9 @@ def load_json_safe(
46
46
  file_path = Path(file_path)
47
47
 
48
48
  try:
49
- with open(file_path, encoding=encoding) as f:
49
+ with Path(file_path).open(
50
+ encoding=encoding,
51
+ ) as f:
50
52
  return json.load(f)
51
53
  except FileNotFoundError:
52
54
  logger.debug(f"JSON file not found: {file_path}")
@@ -112,7 +114,9 @@ def load_yaml_safe(
112
114
  file_path = Path(file_path)
113
115
 
114
116
  try:
115
- with open(file_path, encoding=encoding) as f:
117
+ with Path(file_path).open(
118
+ encoding=encoding,
119
+ ) as f:
116
120
  return yaml.safe_load(f) or default or {}
117
121
  except FileNotFoundError:
118
122
  logger.debug(f"YAML file not found: {file_path}")
@@ -99,7 +99,9 @@ class ConfigurationManager:
99
99
 
100
100
  logger.debug(f"Loading JSON configuration from {file_path}")
101
101
  try:
102
- with open(file_path, encoding="utf-8") as f:
102
+ with Path(file_path).open(
103
+ encoding="utf-8",
104
+ ) as f:
103
105
  config = json.load(f)
104
106
  self._update_cache(file_path, config)
105
107
  return config
@@ -142,7 +144,9 @@ class ConfigurationManager:
142
144
 
143
145
  logger.debug(f"Loading YAML configuration from {file_path}")
144
146
  try:
145
- with open(file_path, encoding="utf-8") as f:
147
+ with Path(file_path).open(
148
+ encoding="utf-8",
149
+ ) as f:
146
150
  config = yaml.safe_load(f) or {}
147
151
  self._update_cache(file_path, config)
148
152
  return config
@@ -185,7 +189,9 @@ class ConfigurationManager:
185
189
 
186
190
  logger.debug(f"Loading TOML configuration from {file_path}")
187
191
  try:
188
- with open(file_path, encoding="utf-8") as f:
192
+ with Path(file_path).open(
193
+ encoding="utf-8",
194
+ ) as f:
189
195
  config = toml.load(f)
190
196
  self._update_cache(file_path, config)
191
197
  return config
@@ -10,7 +10,7 @@ DESIGN DECISION: We prioritize pure Python implementations over native ones
10
10
  for better cross-platform compatibility, even if they might be slightly slower.
11
11
  """
12
12
 
13
- from typing import Any, Dict, Optional, Tuple
13
+ from typing import Any, ClassVar, Dict, Optional, Tuple
14
14
 
15
15
  from ..core.logger import get_logger
16
16
 
@@ -26,19 +26,19 @@ class DatabaseConnector:
26
26
  """
27
27
 
28
28
  # Database drivers in order of preference (first is preferred)
29
- MYSQL_DRIVERS = [
29
+ MYSQL_DRIVERS: ClassVar[list] = [
30
30
  ("pymysql", "pymysql"), # Pure Python, no compilation required
31
31
  ("mysqlclient", "MySQLdb"), # Faster but requires MySQL dev headers
32
32
  ("mysql-connector-python", "mysql.connector"), # Oracle's pure Python driver
33
33
  ]
34
34
 
35
- POSTGRESQL_DRIVERS = [
35
+ POSTGRESQL_DRIVERS: ClassVar[list] = [
36
36
  ("psycopg2-binary", "psycopg2"), # Binary wheel, no compilation
37
37
  ("psycopg2", "psycopg2"), # Requires PostgreSQL dev headers
38
38
  ("pg8000", "pg8000"), # Pure Python alternative
39
39
  ]
40
40
 
41
- ORACLE_DRIVERS = [
41
+ ORACLE_DRIVERS: ClassVar[list] = [
42
42
  ("cx_Oracle", "cx_Oracle"), # Requires Oracle client libraries
43
43
  ("oracledb", "oracledb"), # Pure Python Oracle driver (newer)
44
44
  ]
@@ -106,7 +106,7 @@ class DependencyStrategy:
106
106
  def _is_docker(self) -> bool:
107
107
  """Check if running inside Docker container."""
108
108
  return (
109
- os.path.exists("/.dockerenv")
109
+ Path("/.dockerenv").exists()
110
110
  or os.environ.get("KUBERNETES_SERVICE_HOST") is not None
111
111
  )
112
112
 
@@ -7,6 +7,7 @@ prompting is appropriate for dependency installation.
7
7
 
8
8
  import os
9
9
  import sys
10
+ from pathlib import Path
10
11
  from typing import Dict, Tuple
11
12
 
12
13
  from ..core.logger import get_logger
@@ -139,7 +140,7 @@ class EnvironmentContext:
139
140
  """
140
141
  # Check for Docker-specific files
141
142
  for indicator_file in cls.CONTAINER_INDICATORS:
142
- if os.path.exists(indicator_file):
143
+ if Path(indicator_file).exists():
143
144
  logger.debug(f"Container detected via {indicator_file}")
144
145
  return True
145
146
 
@@ -149,7 +150,7 @@ class EnvironmentContext:
149
150
 
150
151
  # Check cgroup for docker/containerd references
151
152
  try:
152
- with open("/proc/1/cgroup") as f:
153
+ with Path("/proc/1/cgroup").open() as f:
153
154
  cgroup_content = f.read()
154
155
  if "docker" in cgroup_content or "containerd" in cgroup_content:
155
156
  return True
@@ -7,7 +7,6 @@ error handling, and directory management.
7
7
  """
8
8
 
9
9
  import json
10
- import os
11
10
  import shutil
12
11
  import tempfile
13
12
  from pathlib import Path
@@ -149,7 +148,7 @@ def atomic_write(
149
148
  # Clean up temporary file if it exists
150
149
  try:
151
150
  if "temp_path" in locals():
152
- os.unlink(temp_path)
151
+ Path(temp_path).unlink()
153
152
  except Exception:
154
153
  pass
155
154
  raise FileOperationError(f"Failed to atomically write file {path}: {e}") from e
@@ -136,7 +136,9 @@ class PathOperations:
136
136
  logger.warning(f"File not readable: {path}")
137
137
  return default
138
138
 
139
- with open(path_obj, encoding=encoding) as f:
139
+ with Path(path_obj).open(
140
+ encoding=encoding,
141
+ ) as f:
140
142
  return f.read()
141
143
  except Exception as e:
142
144
  logger.error(f"Error reading file {path}: {e}")
@@ -100,10 +100,9 @@ class RobustPackageInstaller:
100
100
  Tuple of (success, error_message)
101
101
  """
102
102
  # Check success cache first
103
- if package_spec in self.success_cache:
104
- if self.success_cache[package_spec]:
105
- logger.debug(f"Package {package_spec} already successfully installed")
106
- return True, None
103
+ if self.success_cache.get(package_spec):
104
+ logger.debug(f"Package {package_spec} already successfully installed")
105
+ return True, None
107
106
 
108
107
  # Default strategy order
109
108
  if strategies is None:
@@ -8,7 +8,7 @@ Critical for ensuring agents work correctly with Claude Code.
8
8
  """
9
9
 
10
10
  import re
11
- from typing import Dict, List, Optional, Tuple
11
+ from typing import ClassVar, Dict, List, Optional, Tuple
12
12
 
13
13
  import yaml
14
14
 
@@ -21,7 +21,7 @@ class FrontmatterValidator:
21
21
  NAME_PATTERN = re.compile(r"^[a-z0-9]+(-[a-z0-9]+)*$")
22
22
 
23
23
  # Valid tool names (from Claude Code spec)
24
- VALID_TOOLS = {
24
+ VALID_TOOLS: ClassVar[set] = {
25
25
  "Read",
26
26
  "Write",
27
27
  "Edit",
@@ -41,10 +41,10 @@ class FrontmatterValidator:
41
41
  }
42
42
 
43
43
  # Valid model tiers
44
- VALID_MODELS = {"opus", "sonnet", "haiku"}
44
+ VALID_MODELS: ClassVar[set] = {"opus", "sonnet", "haiku"}
45
45
 
46
46
  # Required fields in frontmatter
47
- REQUIRED_FIELDS = {"name", "description", "tools"}
47
+ REQUIRED_FIELDS: ClassVar[set] = {"name", "description", "tools"}
48
48
 
49
49
  @classmethod
50
50
  def validate_name(cls, name: str) -> Tuple[bool, Optional[str]]:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: claude-mpm
3
- Version: 4.6.1
3
+ Version: 4.7.0
4
4
  Summary: Claude Multi-Agent Project Manager - Orchestrate Claude with agent delegation and ticket tracking
5
5
  Author-email: Bob Matsuoka <bob@matsuoka.com>
6
6
  Maintainer: Claude MPM Team