claude-mpm 0.3.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.

Files changed (159) hide show
  1. claude_mpm/__init__.py +17 -0
  2. claude_mpm/__main__.py +14 -0
  3. claude_mpm/_version.py +32 -0
  4. claude_mpm/agents/BASE_AGENT_TEMPLATE.md +88 -0
  5. claude_mpm/agents/INSTRUCTIONS.md +375 -0
  6. claude_mpm/agents/__init__.py +118 -0
  7. claude_mpm/agents/agent_loader.py +621 -0
  8. claude_mpm/agents/agent_loader_integration.py +229 -0
  9. claude_mpm/agents/agents_metadata.py +204 -0
  10. claude_mpm/agents/base_agent.json +27 -0
  11. claude_mpm/agents/base_agent_loader.py +519 -0
  12. claude_mpm/agents/schema/agent_schema.json +160 -0
  13. claude_mpm/agents/system_agent_config.py +587 -0
  14. claude_mpm/agents/templates/__init__.py +101 -0
  15. claude_mpm/agents/templates/data_engineer_agent.json +46 -0
  16. claude_mpm/agents/templates/documentation_agent.json +45 -0
  17. claude_mpm/agents/templates/engineer_agent.json +49 -0
  18. claude_mpm/agents/templates/ops_agent.json +46 -0
  19. claude_mpm/agents/templates/qa_agent.json +45 -0
  20. claude_mpm/agents/templates/research_agent.json +49 -0
  21. claude_mpm/agents/templates/security_agent.json +46 -0
  22. claude_mpm/agents/templates/update-optimized-specialized-agents.json +374 -0
  23. claude_mpm/agents/templates/version_control_agent.json +46 -0
  24. claude_mpm/agents/test_fix_deployment/.claude-pm/config/project.json +6 -0
  25. claude_mpm/cli.py +655 -0
  26. claude_mpm/cli_main.py +13 -0
  27. claude_mpm/cli_module/__init__.py +15 -0
  28. claude_mpm/cli_module/args.py +222 -0
  29. claude_mpm/cli_module/commands.py +203 -0
  30. claude_mpm/cli_module/migration_example.py +183 -0
  31. claude_mpm/cli_module/refactoring_guide.md +253 -0
  32. claude_mpm/cli_old/__init__.py +1 -0
  33. claude_mpm/cli_old/ticket_cli.py +102 -0
  34. claude_mpm/config/__init__.py +5 -0
  35. claude_mpm/config/hook_config.py +42 -0
  36. claude_mpm/constants.py +150 -0
  37. claude_mpm/core/__init__.py +45 -0
  38. claude_mpm/core/agent_name_normalizer.py +248 -0
  39. claude_mpm/core/agent_registry.py +627 -0
  40. claude_mpm/core/agent_registry.py.bak +312 -0
  41. claude_mpm/core/agent_session_manager.py +273 -0
  42. claude_mpm/core/base_service.py +747 -0
  43. claude_mpm/core/base_service.py.bak +406 -0
  44. claude_mpm/core/config.py +334 -0
  45. claude_mpm/core/config_aliases.py +292 -0
  46. claude_mpm/core/container.py +347 -0
  47. claude_mpm/core/factories.py +281 -0
  48. claude_mpm/core/framework_loader.py +472 -0
  49. claude_mpm/core/injectable_service.py +206 -0
  50. claude_mpm/core/interfaces.py +539 -0
  51. claude_mpm/core/logger.py +468 -0
  52. claude_mpm/core/minimal_framework_loader.py +107 -0
  53. claude_mpm/core/mixins.py +150 -0
  54. claude_mpm/core/service_registry.py +299 -0
  55. claude_mpm/core/session_manager.py +190 -0
  56. claude_mpm/core/simple_runner.py +511 -0
  57. claude_mpm/core/tool_access_control.py +173 -0
  58. claude_mpm/hooks/README.md +243 -0
  59. claude_mpm/hooks/__init__.py +5 -0
  60. claude_mpm/hooks/base_hook.py +154 -0
  61. claude_mpm/hooks/builtin/__init__.py +1 -0
  62. claude_mpm/hooks/builtin/logging_hook_example.py +165 -0
  63. claude_mpm/hooks/builtin/post_delegation_hook_example.py +124 -0
  64. claude_mpm/hooks/builtin/pre_delegation_hook_example.py +125 -0
  65. claude_mpm/hooks/builtin/submit_hook_example.py +100 -0
  66. claude_mpm/hooks/builtin/ticket_extraction_hook_example.py +237 -0
  67. claude_mpm/hooks/builtin/todo_agent_prefix_hook.py +239 -0
  68. claude_mpm/hooks/builtin/workflow_start_hook.py +181 -0
  69. claude_mpm/hooks/hook_client.py +264 -0
  70. claude_mpm/hooks/hook_runner.py +370 -0
  71. claude_mpm/hooks/json_rpc_executor.py +259 -0
  72. claude_mpm/hooks/json_rpc_hook_client.py +319 -0
  73. claude_mpm/hooks/tool_call_interceptor.py +204 -0
  74. claude_mpm/init.py +246 -0
  75. claude_mpm/orchestration/SUBPROCESS_DESIGN.md +66 -0
  76. claude_mpm/orchestration/__init__.py +6 -0
  77. claude_mpm/orchestration/archive/direct_orchestrator.py +195 -0
  78. claude_mpm/orchestration/archive/factory.py +215 -0
  79. claude_mpm/orchestration/archive/hook_enabled_orchestrator.py +188 -0
  80. claude_mpm/orchestration/archive/hook_integration_example.py +178 -0
  81. claude_mpm/orchestration/archive/interactive_subprocess_orchestrator.py +826 -0
  82. claude_mpm/orchestration/archive/orchestrator.py +501 -0
  83. claude_mpm/orchestration/archive/pexpect_orchestrator.py +252 -0
  84. claude_mpm/orchestration/archive/pty_orchestrator.py +270 -0
  85. claude_mpm/orchestration/archive/simple_orchestrator.py +82 -0
  86. claude_mpm/orchestration/archive/subprocess_orchestrator.py +801 -0
  87. claude_mpm/orchestration/archive/system_prompt_orchestrator.py +278 -0
  88. claude_mpm/orchestration/archive/wrapper_orchestrator.py +187 -0
  89. claude_mpm/scripts/__init__.py +1 -0
  90. claude_mpm/scripts/ticket.py +269 -0
  91. claude_mpm/services/__init__.py +10 -0
  92. claude_mpm/services/agent_deployment.py +955 -0
  93. claude_mpm/services/agent_lifecycle_manager.py +948 -0
  94. claude_mpm/services/agent_management_service.py +596 -0
  95. claude_mpm/services/agent_modification_tracker.py +841 -0
  96. claude_mpm/services/agent_profile_loader.py +606 -0
  97. claude_mpm/services/agent_registry.py +677 -0
  98. claude_mpm/services/base_agent_manager.py +380 -0
  99. claude_mpm/services/framework_agent_loader.py +337 -0
  100. claude_mpm/services/framework_claude_md_generator/README.md +92 -0
  101. claude_mpm/services/framework_claude_md_generator/__init__.py +206 -0
  102. claude_mpm/services/framework_claude_md_generator/content_assembler.py +151 -0
  103. claude_mpm/services/framework_claude_md_generator/content_validator.py +126 -0
  104. claude_mpm/services/framework_claude_md_generator/deployment_manager.py +137 -0
  105. claude_mpm/services/framework_claude_md_generator/section_generators/__init__.py +106 -0
  106. claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +582 -0
  107. claude_mpm/services/framework_claude_md_generator/section_generators/claude_pm_init.py +97 -0
  108. claude_mpm/services/framework_claude_md_generator/section_generators/core_responsibilities.py +27 -0
  109. claude_mpm/services/framework_claude_md_generator/section_generators/delegation_constraints.py +23 -0
  110. claude_mpm/services/framework_claude_md_generator/section_generators/environment_config.py +23 -0
  111. claude_mpm/services/framework_claude_md_generator/section_generators/footer.py +20 -0
  112. claude_mpm/services/framework_claude_md_generator/section_generators/header.py +26 -0
  113. claude_mpm/services/framework_claude_md_generator/section_generators/orchestration_principles.py +30 -0
  114. claude_mpm/services/framework_claude_md_generator/section_generators/role_designation.py +37 -0
  115. claude_mpm/services/framework_claude_md_generator/section_generators/subprocess_validation.py +111 -0
  116. claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +89 -0
  117. claude_mpm/services/framework_claude_md_generator/section_generators/troubleshooting.py +39 -0
  118. claude_mpm/services/framework_claude_md_generator/section_manager.py +106 -0
  119. claude_mpm/services/framework_claude_md_generator/version_manager.py +121 -0
  120. claude_mpm/services/framework_claude_md_generator.py +621 -0
  121. claude_mpm/services/hook_service.py +388 -0
  122. claude_mpm/services/hook_service_manager.py +223 -0
  123. claude_mpm/services/json_rpc_hook_manager.py +92 -0
  124. claude_mpm/services/parent_directory_manager/README.md +83 -0
  125. claude_mpm/services/parent_directory_manager/__init__.py +577 -0
  126. claude_mpm/services/parent_directory_manager/backup_manager.py +258 -0
  127. claude_mpm/services/parent_directory_manager/config_manager.py +210 -0
  128. claude_mpm/services/parent_directory_manager/deduplication_manager.py +279 -0
  129. claude_mpm/services/parent_directory_manager/framework_protector.py +143 -0
  130. claude_mpm/services/parent_directory_manager/operations.py +186 -0
  131. claude_mpm/services/parent_directory_manager/state_manager.py +624 -0
  132. claude_mpm/services/parent_directory_manager/template_deployer.py +579 -0
  133. claude_mpm/services/parent_directory_manager/validation_manager.py +378 -0
  134. claude_mpm/services/parent_directory_manager/version_control_helper.py +339 -0
  135. claude_mpm/services/parent_directory_manager/version_manager.py +222 -0
  136. claude_mpm/services/shared_prompt_cache.py +819 -0
  137. claude_mpm/services/ticket_manager.py +213 -0
  138. claude_mpm/services/ticket_manager_di.py +318 -0
  139. claude_mpm/services/ticketing_service_original.py +508 -0
  140. claude_mpm/services/version_control/VERSION +1 -0
  141. claude_mpm/services/version_control/__init__.py +70 -0
  142. claude_mpm/services/version_control/branch_strategy.py +670 -0
  143. claude_mpm/services/version_control/conflict_resolution.py +744 -0
  144. claude_mpm/services/version_control/git_operations.py +784 -0
  145. claude_mpm/services/version_control/semantic_versioning.py +703 -0
  146. claude_mpm/ui/__init__.py +1 -0
  147. claude_mpm/ui/rich_terminal_ui.py +295 -0
  148. claude_mpm/ui/terminal_ui.py +328 -0
  149. claude_mpm/utils/__init__.py +16 -0
  150. claude_mpm/utils/config_manager.py +468 -0
  151. claude_mpm/utils/import_migration_example.py +80 -0
  152. claude_mpm/utils/imports.py +182 -0
  153. claude_mpm/utils/path_operations.py +357 -0
  154. claude_mpm/utils/paths.py +289 -0
  155. claude_mpm-0.3.0.dist-info/METADATA +290 -0
  156. claude_mpm-0.3.0.dist-info/RECORD +159 -0
  157. claude_mpm-0.3.0.dist-info/WHEEL +5 -0
  158. claude_mpm-0.3.0.dist-info/entry_points.txt +4 -0
  159. claude_mpm-0.3.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,468 @@
1
+ """Configuration management utility for Claude MPM.
2
+
3
+ This module provides a unified interface for loading, saving, and managing
4
+ configurations across different file formats (JSON, YAML, TOML).
5
+ """
6
+
7
+ import json
8
+ import logging
9
+ import os
10
+ from pathlib import Path
11
+ from typing import Any, Dict, List, Optional, Union
12
+ from functools import lru_cache
13
+
14
+ # Handle optional imports
15
+ try:
16
+ import yaml
17
+ HAS_YAML = True
18
+ except ImportError:
19
+ HAS_YAML = False
20
+
21
+ try:
22
+ import toml
23
+ HAS_TOML = True
24
+ except ImportError:
25
+ HAS_TOML = False
26
+
27
+ from ..core.logger import get_logger
28
+
29
+ logger = get_logger(__name__)
30
+
31
+
32
+ class ConfigurationManager:
33
+ """Unified configuration management with support for multiple formats."""
34
+
35
+ def __init__(self, cache_enabled: bool = True):
36
+ """Initialize the configuration manager.
37
+
38
+ Args:
39
+ cache_enabled: Whether to enable configuration caching
40
+ """
41
+ self.cache_enabled = cache_enabled
42
+ self._cache: Dict[str, Any] = {}
43
+
44
+ def _get_cache_key(self, file_path: Union[str, Path]) -> str:
45
+ """Generate a cache key for a configuration file."""
46
+ path = Path(file_path)
47
+ stat = path.stat()
48
+ return f"{path.absolute()}:{stat.st_mtime}:{stat.st_size}"
49
+
50
+ def _check_cache(self, file_path: Union[str, Path]) -> Optional[Dict[str, Any]]:
51
+ """Check if configuration is cached and still valid."""
52
+ if not self.cache_enabled:
53
+ return None
54
+
55
+ try:
56
+ cache_key = self._get_cache_key(file_path)
57
+ return self._cache.get(cache_key)
58
+ except (OSError, IOError):
59
+ return None
60
+
61
+ def _update_cache(self, file_path: Union[str, Path], config: Dict[str, Any]):
62
+ """Update the configuration cache."""
63
+ if not self.cache_enabled:
64
+ return
65
+
66
+ try:
67
+ cache_key = self._get_cache_key(file_path)
68
+ self._cache[cache_key] = config
69
+ except (OSError, IOError):
70
+ pass
71
+
72
+ def clear_cache(self):
73
+ """Clear the configuration cache."""
74
+ self._cache.clear()
75
+
76
+ def load_json(self, file_path: Union[str, Path]) -> Dict[str, Any]:
77
+ """Load JSON configuration file.
78
+
79
+ Args:
80
+ file_path: Path to JSON file
81
+
82
+ Returns:
83
+ Configuration dictionary
84
+
85
+ Raises:
86
+ FileNotFoundError: If file doesn't exist
87
+ json.JSONDecodeError: If JSON is invalid
88
+ """
89
+ file_path = Path(file_path)
90
+
91
+ # Check cache
92
+ cached = self._check_cache(file_path)
93
+ if cached is not None:
94
+ logger.debug(f"Using cached configuration for {file_path}")
95
+ return cached
96
+
97
+ if not file_path.exists():
98
+ raise FileNotFoundError(f"Configuration file not found: {file_path}")
99
+
100
+ logger.debug(f"Loading JSON configuration from {file_path}")
101
+ try:
102
+ with open(file_path, 'r', encoding='utf-8') as f:
103
+ config = json.load(f)
104
+ self._update_cache(file_path, config)
105
+ return config
106
+ except json.JSONDecodeError as e:
107
+ logger.error(f"Invalid JSON in {file_path}: {e}")
108
+ raise
109
+ except Exception as e:
110
+ logger.error(f"Error loading JSON from {file_path}: {e}")
111
+ raise
112
+
113
+ def load_yaml(self, file_path: Union[str, Path]) -> Dict[str, Any]:
114
+ """Load YAML configuration file.
115
+
116
+ Args:
117
+ file_path: Path to YAML file
118
+
119
+ Returns:
120
+ Configuration dictionary
121
+
122
+ Raises:
123
+ ImportError: If PyYAML is not installed
124
+ FileNotFoundError: If file doesn't exist
125
+ yaml.YAMLError: If YAML is invalid
126
+ """
127
+ if not HAS_YAML:
128
+ raise ImportError("PyYAML is required for YAML support. Install with: pip install pyyaml")
129
+
130
+ file_path = Path(file_path)
131
+
132
+ # Check cache
133
+ cached = self._check_cache(file_path)
134
+ if cached is not None:
135
+ logger.debug(f"Using cached configuration for {file_path}")
136
+ return cached
137
+
138
+ if not file_path.exists():
139
+ raise FileNotFoundError(f"Configuration file not found: {file_path}")
140
+
141
+ logger.debug(f"Loading YAML configuration from {file_path}")
142
+ try:
143
+ with open(file_path, 'r', encoding='utf-8') as f:
144
+ config = yaml.safe_load(f) or {}
145
+ self._update_cache(file_path, config)
146
+ return config
147
+ except yaml.YAMLError as e:
148
+ logger.error(f"Invalid YAML in {file_path}: {e}")
149
+ raise
150
+ except Exception as e:
151
+ logger.error(f"Error loading YAML from {file_path}: {e}")
152
+ raise
153
+
154
+ def load_toml(self, file_path: Union[str, Path]) -> Dict[str, Any]:
155
+ """Load TOML configuration file.
156
+
157
+ Args:
158
+ file_path: Path to TOML file
159
+
160
+ Returns:
161
+ Configuration dictionary
162
+
163
+ Raises:
164
+ ImportError: If toml is not installed
165
+ FileNotFoundError: If file doesn't exist
166
+ toml.TomlDecodeError: If TOML is invalid
167
+ """
168
+ if not HAS_TOML:
169
+ raise ImportError("toml is required for TOML support. Install with: pip install toml")
170
+
171
+ file_path = Path(file_path)
172
+
173
+ # Check cache
174
+ cached = self._check_cache(file_path)
175
+ if cached is not None:
176
+ logger.debug(f"Using cached configuration for {file_path}")
177
+ return cached
178
+
179
+ if not file_path.exists():
180
+ raise FileNotFoundError(f"Configuration file not found: {file_path}")
181
+
182
+ logger.debug(f"Loading TOML configuration from {file_path}")
183
+ try:
184
+ with open(file_path, 'r', encoding='utf-8') as f:
185
+ config = toml.load(f)
186
+ self._update_cache(file_path, config)
187
+ return config
188
+ except toml.TomlDecodeError as e:
189
+ logger.error(f"Invalid TOML in {file_path}: {e}")
190
+ raise
191
+ except Exception as e:
192
+ logger.error(f"Error loading TOML from {file_path}: {e}")
193
+ raise
194
+
195
+ def load_auto(self, file_path: Union[str, Path]) -> Dict[str, Any]:
196
+ """Auto-detect format and load configuration file.
197
+
198
+ Args:
199
+ file_path: Path to configuration file
200
+
201
+ Returns:
202
+ Configuration dictionary
203
+
204
+ Raises:
205
+ ValueError: If file format is not supported
206
+ FileNotFoundError: If file doesn't exist
207
+ """
208
+ file_path = Path(file_path)
209
+ suffix = file_path.suffix.lower()
210
+
211
+ if suffix in ['.json']:
212
+ return self.load_json(file_path)
213
+ elif suffix in ['.yaml', '.yml']:
214
+ return self.load_yaml(file_path)
215
+ elif suffix in ['.toml']:
216
+ return self.load_toml(file_path)
217
+ else:
218
+ raise ValueError(f"Unsupported configuration format: {suffix}")
219
+
220
+ def save_json(self, config: Dict[str, Any], file_path: Union[str, Path],
221
+ indent: int = 2, sort_keys: bool = True):
222
+ """Save configuration as JSON.
223
+
224
+ Args:
225
+ config: Configuration dictionary
226
+ file_path: Path to save JSON file
227
+ indent: JSON indentation (default: 2)
228
+ sort_keys: Whether to sort keys (default: True)
229
+ """
230
+ file_path = Path(file_path)
231
+ logger.debug(f"Saving JSON configuration to {file_path}")
232
+
233
+ try:
234
+ # Create parent directories if needed
235
+ file_path.parent.mkdir(parents=True, exist_ok=True)
236
+
237
+ with open(file_path, 'w', encoding='utf-8') as f:
238
+ json.dump(config, f, indent=indent, sort_keys=sort_keys)
239
+ logger.info(f"Configuration saved to {file_path}")
240
+ except Exception as e:
241
+ logger.error(f"Error saving JSON to {file_path}: {e}")
242
+ raise
243
+
244
+ def save_yaml(self, config: Dict[str, Any], file_path: Union[str, Path],
245
+ default_flow_style: bool = False, sort_keys: bool = True):
246
+ """Save configuration as YAML.
247
+
248
+ Args:
249
+ config: Configuration dictionary
250
+ file_path: Path to save YAML file
251
+ default_flow_style: Use flow style (default: False)
252
+ sort_keys: Whether to sort keys (default: True)
253
+
254
+ Raises:
255
+ ImportError: If PyYAML is not installed
256
+ """
257
+ if not HAS_YAML:
258
+ raise ImportError("PyYAML is required for YAML support. Install with: pip install pyyaml")
259
+
260
+ file_path = Path(file_path)
261
+ logger.debug(f"Saving YAML configuration to {file_path}")
262
+
263
+ try:
264
+ # Create parent directories if needed
265
+ file_path.parent.mkdir(parents=True, exist_ok=True)
266
+
267
+ with open(file_path, 'w', encoding='utf-8') as f:
268
+ yaml.dump(config, f, default_flow_style=default_flow_style,
269
+ sort_keys=sort_keys)
270
+ logger.info(f"Configuration saved to {file_path}")
271
+ except Exception as e:
272
+ logger.error(f"Error saving YAML to {file_path}: {e}")
273
+ raise
274
+
275
+ def save_toml(self, config: Dict[str, Any], file_path: Union[str, Path]):
276
+ """Save configuration as TOML.
277
+
278
+ Args:
279
+ config: Configuration dictionary
280
+ file_path: Path to save TOML file
281
+
282
+ Raises:
283
+ ImportError: If toml is not installed
284
+ """
285
+ if not HAS_TOML:
286
+ raise ImportError("toml is required for TOML support. Install with: pip install toml")
287
+
288
+ file_path = Path(file_path)
289
+ logger.debug(f"Saving TOML configuration to {file_path}")
290
+
291
+ try:
292
+ # Create parent directories if needed
293
+ file_path.parent.mkdir(parents=True, exist_ok=True)
294
+
295
+ with open(file_path, 'w', encoding='utf-8') as f:
296
+ toml.dump(config, f)
297
+ logger.info(f"Configuration saved to {file_path}")
298
+ except Exception as e:
299
+ logger.error(f"Error saving TOML to {file_path}: {e}")
300
+ raise
301
+
302
+ def merge_configs(self, *configs: Dict[str, Any]) -> Dict[str, Any]:
303
+ """Merge multiple configurations.
304
+
305
+ Later configurations override earlier ones. Nested dictionaries
306
+ are merged recursively.
307
+
308
+ Args:
309
+ *configs: Configuration dictionaries to merge
310
+
311
+ Returns:
312
+ Merged configuration dictionary
313
+ """
314
+ result = {}
315
+
316
+ for config in configs:
317
+ self._deep_merge(result, config)
318
+
319
+ return result
320
+
321
+ def _deep_merge(self, target: Dict[str, Any], source: Dict[str, Any]):
322
+ """Deep merge source into target dictionary."""
323
+ for key, value in source.items():
324
+ if key in target and isinstance(target[key], dict) and isinstance(value, dict):
325
+ self._deep_merge(target[key], value)
326
+ else:
327
+ target[key] = value
328
+
329
+ def validate_schema(self, config: Dict[str, Any], schema: Dict[str, Any]) -> List[str]:
330
+ """Validate configuration against a schema.
331
+
332
+ Args:
333
+ config: Configuration to validate
334
+ schema: Schema dictionary defining required fields and types
335
+
336
+ Returns:
337
+ List of validation errors (empty if valid)
338
+
339
+ Example schema:
340
+ {
341
+ "required": ["field1", "field2"],
342
+ "types": {
343
+ "field1": str,
344
+ "field2": int,
345
+ "field3": list
346
+ }
347
+ }
348
+ """
349
+ errors = []
350
+
351
+ # Check required fields
352
+ required = schema.get("required", [])
353
+ for field in required:
354
+ if field not in config:
355
+ errors.append(f"Required field missing: {field}")
356
+
357
+ # Check types
358
+ types = schema.get("types", {})
359
+ for field, expected_type in types.items():
360
+ if field in config:
361
+ if not isinstance(config[field], expected_type):
362
+ actual_type = type(config[field]).__name__
363
+ expected_name = expected_type.__name__
364
+ errors.append(f"Invalid type for {field}: expected {expected_name}, got {actual_type}")
365
+
366
+ return errors
367
+
368
+ def get_with_default(self, config: Dict[str, Any], key: str,
369
+ default: Any = None, separator: str = ".") -> Any:
370
+ """Get configuration value with default fallback.
371
+
372
+ Supports nested keys using dot notation.
373
+
374
+ Args:
375
+ config: Configuration dictionary
376
+ key: Key to retrieve (supports dot notation for nested keys)
377
+ default: Default value if key not found
378
+ separator: Key separator for nested access (default: ".")
379
+
380
+ Returns:
381
+ Configuration value or default
382
+
383
+ Example:
384
+ get_with_default(config, "database.host", "localhost")
385
+ """
386
+ keys = key.split(separator)
387
+ value = config
388
+
389
+ for k in keys:
390
+ if isinstance(value, dict) and k in value:
391
+ value = value[k]
392
+ else:
393
+ return default
394
+
395
+ return value
396
+
397
+ def interpolate_env(self, config: Dict[str, Any],
398
+ pattern: str = "${%s}") -> Dict[str, Any]:
399
+ """Interpolate environment variables in configuration.
400
+
401
+ Args:
402
+ config: Configuration dictionary
403
+ pattern: Pattern for environment variables (default: "${VAR}")
404
+
405
+ Returns:
406
+ Configuration with environment variables interpolated
407
+
408
+ Example:
409
+ Input: {"host": "${DB_HOST}", "port": "${DB_PORT}"}
410
+ Output: {"host": "localhost", "port": "5432"}
411
+ """
412
+ import re
413
+
414
+ def _interpolate_value(value: Any) -> Any:
415
+ if isinstance(value, str):
416
+ # Find all environment variable references
417
+ env_pattern = pattern.replace("%s", r"([A-Z_][A-Z0-9_]*)")
418
+ matches = re.findall(env_pattern, value)
419
+
420
+ result = value
421
+ for var_name in matches:
422
+ env_value = os.environ.get(var_name, "")
423
+ placeholder = pattern % var_name
424
+ result = result.replace(placeholder, env_value)
425
+
426
+ return result
427
+ elif isinstance(value, dict):
428
+ return {k: _interpolate_value(v) for k, v in value.items()}
429
+ elif isinstance(value, list):
430
+ return [_interpolate_value(item) for item in value]
431
+ else:
432
+ return value
433
+
434
+ return _interpolate_value(config)
435
+
436
+
437
+ # Convenience functions
438
+ _default_manager = ConfigurationManager()
439
+
440
+ def load_config(file_path: Union[str, Path]) -> Dict[str, Any]:
441
+ """Load configuration file with auto-detection.
442
+
443
+ Args:
444
+ file_path: Path to configuration file
445
+
446
+ Returns:
447
+ Configuration dictionary
448
+ """
449
+ return _default_manager.load_auto(file_path)
450
+
451
+ def save_config(config: Dict[str, Any], file_path: Union[str, Path]):
452
+ """Save configuration file with auto-detection.
453
+
454
+ Args:
455
+ config: Configuration dictionary
456
+ file_path: Path to save configuration
457
+ """
458
+ file_path = Path(file_path)
459
+ suffix = file_path.suffix.lower()
460
+
461
+ if suffix in ['.json']:
462
+ _default_manager.save_json(config, file_path)
463
+ elif suffix in ['.yaml', '.yml']:
464
+ _default_manager.save_yaml(config, file_path)
465
+ elif suffix in ['.toml']:
466
+ _default_manager.save_toml(config, file_path)
467
+ else:
468
+ raise ValueError(f"Unsupported configuration format: {suffix}")
@@ -0,0 +1,80 @@
1
+ """Example of how to migrate duplicate import patterns to use safe_import.
2
+
3
+ This file demonstrates how to replace the common try/except ImportError
4
+ pattern with the new safe_import utility.
5
+ """
6
+
7
+ # BEFORE: The old pattern used throughout the codebase
8
+ # -----------------------------------------------------
9
+ # try:
10
+ # from ..core.logger import get_logger
11
+ # except ImportError:
12
+ # from utils.logger import get_logger
13
+ #
14
+ # try:
15
+ # from ..core.agent_registry import AgentRegistryAdapter
16
+ # except ImportError:
17
+ # from core.agent_registry import AgentRegistryAdapter
18
+
19
+
20
+ # AFTER: Using safe_import utility
21
+ # --------------------------------
22
+ from claude_mpm.utils.imports import safe_import, safe_import_multiple
23
+
24
+ # Method 1: Individual imports
25
+ get_logger = safe_import('..utils.logger', 'utils.logger', from_list=['get_logger'])
26
+ AgentRegistryAdapter = safe_import('..core.agent_registry', 'core.agent_registry',
27
+ from_list=['AgentRegistryAdapter'])
28
+
29
+ # Method 2: Batch imports (recommended for multiple imports)
30
+ imports = safe_import_multiple([
31
+ ('..utils.logger', 'utils.logger', ['get_logger']),
32
+ ('..core.agent_registry', 'core.agent_registry', ['AgentRegistryAdapter']),
33
+ ])
34
+
35
+ get_logger = imports.get('get_logger')
36
+ AgentRegistryAdapter = imports.get('AgentRegistryAdapter')
37
+
38
+
39
+ # MIGRATION GUIDE
40
+ # ---------------
41
+ # 1. Add import: from claude_mpm.utils.imports import safe_import
42
+ #
43
+ # 2. Replace try/except blocks:
44
+ # FROM:
45
+ # try:
46
+ # from ..module import something
47
+ # except ImportError:
48
+ # from module import something
49
+ #
50
+ # TO:
51
+ # something = safe_import('..module', 'module', from_list=['something'])
52
+ #
53
+ # 3. For multiple imports from same module:
54
+ # FROM:
55
+ # try:
56
+ # from ..module import foo, bar, baz
57
+ # except ImportError:
58
+ # from module import foo, bar, baz
59
+ #
60
+ # TO:
61
+ # foo, bar, baz = safe_import('..module', 'module', from_list=['foo', 'bar', 'baz'])
62
+ #
63
+ # 4. For whole module imports:
64
+ # FROM:
65
+ # try:
66
+ # from .. import module
67
+ # except ImportError:
68
+ # import module
69
+ #
70
+ # TO:
71
+ # module = safe_import('..module', 'module')
72
+
73
+
74
+ # BENEFITS
75
+ # --------
76
+ # 1. Reduces code duplication (97 files can be simplified)
77
+ # 2. Centralizes import error handling logic
78
+ # 3. Provides optional logging for debugging import issues
79
+ # 4. Maintains the same functionality with cleaner code
80
+ # 5. Makes the codebase more maintainable