claude-mpm 4.3.22__py3-none-any.whl → 4.4.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/WORKFLOW.md +2 -14
- claude_mpm/cli/commands/configure.py +2 -29
- claude_mpm/cli/commands/doctor.py +2 -2
- claude_mpm/cli/commands/mpm_init.py +3 -3
- claude_mpm/cli/parsers/configure_parser.py +4 -15
- claude_mpm/core/framework/__init__.py +38 -0
- claude_mpm/core/framework/formatters/__init__.py +11 -0
- claude_mpm/core/framework/formatters/capability_generator.py +356 -0
- claude_mpm/core/framework/formatters/content_formatter.py +283 -0
- claude_mpm/core/framework/formatters/context_generator.py +180 -0
- claude_mpm/core/framework/loaders/__init__.py +13 -0
- claude_mpm/core/framework/loaders/agent_loader.py +202 -0
- claude_mpm/core/framework/loaders/file_loader.py +213 -0
- claude_mpm/core/framework/loaders/instruction_loader.py +151 -0
- claude_mpm/core/framework/loaders/packaged_loader.py +208 -0
- claude_mpm/core/framework/processors/__init__.py +11 -0
- claude_mpm/core/framework/processors/memory_processor.py +222 -0
- claude_mpm/core/framework/processors/metadata_processor.py +146 -0
- claude_mpm/core/framework/processors/template_processor.py +238 -0
- claude_mpm/core/framework_loader.py +277 -1798
- claude_mpm/hooks/__init__.py +9 -1
- claude_mpm/hooks/kuzu_memory_hook.py +352 -0
- claude_mpm/hooks/memory_integration_hook.py +1 -1
- claude_mpm/services/agents/memory/content_manager.py +5 -2
- claude_mpm/services/agents/memory/memory_file_service.py +1 -0
- claude_mpm/services/agents/memory/memory_limits_service.py +1 -0
- claude_mpm/services/core/path_resolver.py +1 -0
- claude_mpm/services/diagnostics/diagnostic_runner.py +1 -0
- claude_mpm/services/mcp_config_manager.py +67 -4
- claude_mpm/services/mcp_gateway/core/process_pool.py +281 -0
- claude_mpm/services/mcp_gateway/core/startup_verification.py +2 -2
- claude_mpm/services/mcp_gateway/main.py +3 -13
- claude_mpm/services/mcp_gateway/server/stdio_server.py +4 -10
- claude_mpm/services/mcp_gateway/tools/__init__.py +13 -2
- claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +36 -6
- claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +542 -0
- claude_mpm/services/shared/__init__.py +2 -1
- claude_mpm/services/shared/service_factory.py +8 -5
- claude_mpm/services/unified/__init__.py +65 -0
- claude_mpm/services/unified/analyzer_strategies/__init__.py +44 -0
- claude_mpm/services/unified/analyzer_strategies/code_analyzer.py +473 -0
- claude_mpm/services/unified/analyzer_strategies/dependency_analyzer.py +643 -0
- claude_mpm/services/unified/analyzer_strategies/performance_analyzer.py +804 -0
- claude_mpm/services/unified/analyzer_strategies/security_analyzer.py +661 -0
- claude_mpm/services/unified/analyzer_strategies/structure_analyzer.py +696 -0
- claude_mpm/services/unified/config_strategies/__init__.py +190 -0
- claude_mpm/services/unified/config_strategies/config_schema.py +689 -0
- claude_mpm/services/unified/config_strategies/context_strategy.py +748 -0
- claude_mpm/services/unified/config_strategies/error_handling_strategy.py +999 -0
- claude_mpm/services/unified/config_strategies/file_loader_strategy.py +871 -0
- claude_mpm/services/unified/config_strategies/unified_config_service.py +802 -0
- claude_mpm/services/unified/config_strategies/validation_strategy.py +1105 -0
- claude_mpm/services/unified/deployment_strategies/__init__.py +97 -0
- claude_mpm/services/unified/deployment_strategies/base.py +557 -0
- claude_mpm/services/unified/deployment_strategies/cloud_strategies.py +486 -0
- claude_mpm/services/unified/deployment_strategies/local.py +594 -0
- claude_mpm/services/unified/deployment_strategies/utils.py +672 -0
- claude_mpm/services/unified/deployment_strategies/vercel.py +471 -0
- claude_mpm/services/unified/interfaces.py +499 -0
- claude_mpm/services/unified/migration.py +532 -0
- claude_mpm/services/unified/strategies.py +551 -0
- claude_mpm/services/unified/unified_analyzer.py +534 -0
- claude_mpm/services/unified/unified_config.py +688 -0
- claude_mpm/services/unified/unified_deployment.py +470 -0
- {claude_mpm-4.3.22.dist-info → claude_mpm-4.4.3.dist-info}/METADATA +15 -15
- {claude_mpm-4.3.22.dist-info → claude_mpm-4.4.3.dist-info}/RECORD +71 -32
- claude_mpm/cli/commands/configure_tui.py +0 -1927
- claude_mpm/services/mcp_gateway/tools/ticket_tools.py +0 -645
- claude_mpm/services/mcp_gateway/tools/unified_ticket_tool.py +0 -602
- {claude_mpm-4.3.22.dist-info → claude_mpm-4.4.3.dist-info}/WHEEL +0 -0
- {claude_mpm-4.3.22.dist-info → claude_mpm-4.4.3.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.3.22.dist-info → claude_mpm-4.4.3.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.3.22.dist-info → claude_mpm-4.4.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,688 @@
|
|
1
|
+
"""
|
2
|
+
Unified Configuration Manager Implementation
|
3
|
+
===========================================
|
4
|
+
|
5
|
+
This module implements the unified configuration management service that consolidates
|
6
|
+
all configuration-related services using the strategy pattern. It replaces multiple
|
7
|
+
specialized configuration services with a single, extensible service.
|
8
|
+
|
9
|
+
Consolidates:
|
10
|
+
- ProjectConfigService
|
11
|
+
- AgentConfigService
|
12
|
+
- EnvironmentConfigService
|
13
|
+
- UserConfigService
|
14
|
+
- SystemConfigService
|
15
|
+
- And other configuration-related services
|
16
|
+
|
17
|
+
Features:
|
18
|
+
- Strategy-based configuration handling for different config types
|
19
|
+
- Configuration validation and schema enforcement
|
20
|
+
- Configuration merging with multiple strategies
|
21
|
+
- Hot-reload support for dynamic configuration updates
|
22
|
+
- Version control and rollback capabilities
|
23
|
+
"""
|
24
|
+
|
25
|
+
import asyncio
|
26
|
+
import json
|
27
|
+
from pathlib import Path
|
28
|
+
from typing import Any, Dict, List, Optional, Union
|
29
|
+
|
30
|
+
from claude_mpm.core.logging_utils import get_logger
|
31
|
+
|
32
|
+
from .interfaces import (
|
33
|
+
ConfigurationResult,
|
34
|
+
IConfigurationService,
|
35
|
+
IUnifiedService,
|
36
|
+
ServiceCapability,
|
37
|
+
ServiceMetadata,
|
38
|
+
)
|
39
|
+
from .strategies import ConfigStrategy, StrategyContext, get_strategy_registry
|
40
|
+
|
41
|
+
|
42
|
+
class UnifiedConfigManager(IConfigurationService, IUnifiedService):
|
43
|
+
"""
|
44
|
+
Unified configuration management service using strategy pattern.
|
45
|
+
|
46
|
+
This service consolidates all configuration operations through a
|
47
|
+
pluggable strategy system, providing consistent configuration
|
48
|
+
management across different configuration types.
|
49
|
+
"""
|
50
|
+
|
51
|
+
def __init__(self):
|
52
|
+
"""Initialize unified configuration manager."""
|
53
|
+
self._logger = get_logger(f"{__name__}.UnifiedConfigManager")
|
54
|
+
self._registry = get_strategy_registry()
|
55
|
+
self._configs: Dict[str, Dict[str, Any]] = {}
|
56
|
+
self._config_schemas: Dict[str, Dict[str, Any]] = {}
|
57
|
+
self._config_versions: Dict[str, List[Dict[str, Any]]] = {}
|
58
|
+
self._metrics = {
|
59
|
+
"total_loads": 0,
|
60
|
+
"total_saves": 0,
|
61
|
+
"validation_errors": 0,
|
62
|
+
"merge_operations": 0,
|
63
|
+
"rollbacks": 0,
|
64
|
+
}
|
65
|
+
self._initialized = False
|
66
|
+
self._hot_reload_enabled = False
|
67
|
+
|
68
|
+
def get_metadata(self) -> ServiceMetadata:
|
69
|
+
"""
|
70
|
+
Get service metadata.
|
71
|
+
|
72
|
+
Returns:
|
73
|
+
ServiceMetadata: Service metadata
|
74
|
+
"""
|
75
|
+
return ServiceMetadata(
|
76
|
+
name="UnifiedConfigManager",
|
77
|
+
version="1.0.0",
|
78
|
+
capabilities={
|
79
|
+
ServiceCapability.ASYNC_OPERATIONS,
|
80
|
+
ServiceCapability.VALIDATION,
|
81
|
+
ServiceCapability.HOT_RELOAD,
|
82
|
+
ServiceCapability.VERSIONING,
|
83
|
+
ServiceCapability.ROLLBACK,
|
84
|
+
ServiceCapability.METRICS,
|
85
|
+
ServiceCapability.HEALTH_CHECK,
|
86
|
+
},
|
87
|
+
dependencies=["StrategyRegistry", "LoggingService"],
|
88
|
+
description="Unified service for all configuration management",
|
89
|
+
tags={"configuration", "unified", "strategy-pattern"},
|
90
|
+
deprecated_services=[
|
91
|
+
"ProjectConfigService",
|
92
|
+
"AgentConfigService",
|
93
|
+
"EnvironmentConfigService",
|
94
|
+
"UserConfigService",
|
95
|
+
"SystemConfigService",
|
96
|
+
],
|
97
|
+
)
|
98
|
+
|
99
|
+
async def initialize(self) -> bool:
|
100
|
+
"""
|
101
|
+
Initialize the service.
|
102
|
+
|
103
|
+
Returns:
|
104
|
+
bool: True if initialization successful
|
105
|
+
"""
|
106
|
+
try:
|
107
|
+
self._logger.info("Initializing UnifiedConfigManager")
|
108
|
+
|
109
|
+
# Register default strategies
|
110
|
+
self._register_default_strategies()
|
111
|
+
|
112
|
+
# Load default configurations
|
113
|
+
await self._load_default_configs()
|
114
|
+
|
115
|
+
# Initialize hot reload if enabled
|
116
|
+
if self._hot_reload_enabled:
|
117
|
+
await self._start_hot_reload()
|
118
|
+
|
119
|
+
self._initialized = True
|
120
|
+
self._logger.info("UnifiedConfigManager initialized successfully")
|
121
|
+
return True
|
122
|
+
|
123
|
+
except Exception as e:
|
124
|
+
self._logger.error(f"Failed to initialize: {str(e)}")
|
125
|
+
return False
|
126
|
+
|
127
|
+
async def shutdown(self) -> None:
|
128
|
+
"""Gracefully shutdown the service."""
|
129
|
+
self._logger.info("Shutting down UnifiedConfigManager")
|
130
|
+
|
131
|
+
# Stop hot reload if running
|
132
|
+
if self._hot_reload_enabled:
|
133
|
+
await self._stop_hot_reload()
|
134
|
+
|
135
|
+
# Save current configurations
|
136
|
+
await self._save_all_configs()
|
137
|
+
|
138
|
+
# Clear configurations
|
139
|
+
self._configs.clear()
|
140
|
+
self._config_schemas.clear()
|
141
|
+
self._config_versions.clear()
|
142
|
+
|
143
|
+
self._initialized = False
|
144
|
+
self._logger.info("UnifiedConfigManager shutdown complete")
|
145
|
+
|
146
|
+
def health_check(self) -> Dict[str, Any]:
|
147
|
+
"""
|
148
|
+
Perform health check.
|
149
|
+
|
150
|
+
Returns:
|
151
|
+
Dict[str, Any]: Health status
|
152
|
+
"""
|
153
|
+
strategies = self._registry.list_strategies(ConfigStrategy)
|
154
|
+
|
155
|
+
return {
|
156
|
+
"service": "UnifiedConfigManager",
|
157
|
+
"status": "healthy" if self._initialized else "unhealthy",
|
158
|
+
"initialized": self._initialized,
|
159
|
+
"registered_strategies": len(strategies),
|
160
|
+
"loaded_configs": len(self._configs),
|
161
|
+
"hot_reload_enabled": self._hot_reload_enabled,
|
162
|
+
"metrics": self.get_metrics(),
|
163
|
+
}
|
164
|
+
|
165
|
+
def get_metrics(self) -> Dict[str, Any]:
|
166
|
+
"""
|
167
|
+
Get service metrics.
|
168
|
+
|
169
|
+
Returns:
|
170
|
+
Dict[str, Any]: Service metrics
|
171
|
+
"""
|
172
|
+
error_rate = 0.0
|
173
|
+
total_ops = self._metrics["total_loads"] + self._metrics["total_saves"]
|
174
|
+
if total_ops > 0:
|
175
|
+
error_rate = (self._metrics["validation_errors"] / total_ops) * 100
|
176
|
+
|
177
|
+
return {
|
178
|
+
**self._metrics,
|
179
|
+
"error_rate": error_rate,
|
180
|
+
"loaded_configs": len(self._configs),
|
181
|
+
"total_versions": sum(len(v) for v in self._config_versions.values()),
|
182
|
+
}
|
183
|
+
|
184
|
+
def reset(self) -> None:
|
185
|
+
"""Reset service to initial state."""
|
186
|
+
self._logger.info("Resetting UnifiedConfigManager")
|
187
|
+
self._configs.clear()
|
188
|
+
self._config_schemas.clear()
|
189
|
+
self._config_versions.clear()
|
190
|
+
self._metrics = {
|
191
|
+
"total_loads": 0,
|
192
|
+
"total_saves": 0,
|
193
|
+
"validation_errors": 0,
|
194
|
+
"merge_operations": 0,
|
195
|
+
"rollbacks": 0,
|
196
|
+
}
|
197
|
+
|
198
|
+
def load_config(
|
199
|
+
self, source: Union[str, Path, Dict[str, Any]]
|
200
|
+
) -> ConfigurationResult:
|
201
|
+
"""
|
202
|
+
Load configuration from source.
|
203
|
+
|
204
|
+
Args:
|
205
|
+
source: Configuration source
|
206
|
+
|
207
|
+
Returns:
|
208
|
+
ConfigurationResult: Loaded configuration
|
209
|
+
"""
|
210
|
+
self._metrics["total_loads"] += 1
|
211
|
+
|
212
|
+
try:
|
213
|
+
# Determine config type
|
214
|
+
config_type = self._determine_config_type(source)
|
215
|
+
|
216
|
+
# Select configuration strategy
|
217
|
+
context = StrategyContext(
|
218
|
+
target_type=config_type,
|
219
|
+
operation="load",
|
220
|
+
parameters={"source": source},
|
221
|
+
)
|
222
|
+
|
223
|
+
strategy = self._registry.select_strategy(ConfigStrategy, context)
|
224
|
+
|
225
|
+
if not strategy:
|
226
|
+
self._metrics["validation_errors"] += 1
|
227
|
+
return ConfigurationResult(
|
228
|
+
success=False,
|
229
|
+
validation_errors=[
|
230
|
+
f"No strategy available for config type: {config_type}"
|
231
|
+
],
|
232
|
+
)
|
233
|
+
|
234
|
+
# Load configuration using strategy
|
235
|
+
self._logger.info(
|
236
|
+
f"Loading configuration from {source} using {strategy.metadata.name}"
|
237
|
+
)
|
238
|
+
|
239
|
+
config = strategy.load(source)
|
240
|
+
|
241
|
+
# Validate configuration
|
242
|
+
validation_errors = strategy.validate(config)
|
243
|
+
if validation_errors:
|
244
|
+
self._metrics["validation_errors"] += 1
|
245
|
+
return ConfigurationResult(
|
246
|
+
success=False,
|
247
|
+
config=config,
|
248
|
+
validation_errors=validation_errors,
|
249
|
+
source=str(source),
|
250
|
+
)
|
251
|
+
|
252
|
+
# Apply defaults
|
253
|
+
config_with_defaults = self._apply_strategy_defaults(strategy, config)
|
254
|
+
|
255
|
+
# Store configuration
|
256
|
+
config_id = self._generate_config_id(source, config_type)
|
257
|
+
self._configs[config_id] = config_with_defaults
|
258
|
+
|
259
|
+
# Store schema
|
260
|
+
self._config_schemas[config_id] = strategy.get_schema()
|
261
|
+
|
262
|
+
# Add to version history
|
263
|
+
self._add_to_version_history(config_id, config_with_defaults)
|
264
|
+
|
265
|
+
return ConfigurationResult(
|
266
|
+
success=True,
|
267
|
+
config=config_with_defaults,
|
268
|
+
validation_errors=[],
|
269
|
+
applied_defaults=self._get_applied_defaults(config, config_with_defaults),
|
270
|
+
source=str(source),
|
271
|
+
)
|
272
|
+
|
273
|
+
except Exception as e:
|
274
|
+
self._logger.error(f"Failed to load configuration: {str(e)}")
|
275
|
+
self._metrics["validation_errors"] += 1
|
276
|
+
return ConfigurationResult(
|
277
|
+
success=False,
|
278
|
+
validation_errors=[f"Load failed: {str(e)}"],
|
279
|
+
)
|
280
|
+
|
281
|
+
def save_config(
|
282
|
+
self, config: Dict[str, Any], target: Union[str, Path]
|
283
|
+
) -> ConfigurationResult:
|
284
|
+
"""
|
285
|
+
Save configuration to target.
|
286
|
+
|
287
|
+
Args:
|
288
|
+
config: Configuration to save
|
289
|
+
target: Target location
|
290
|
+
|
291
|
+
Returns:
|
292
|
+
ConfigurationResult: Save result
|
293
|
+
"""
|
294
|
+
self._metrics["total_saves"] += 1
|
295
|
+
|
296
|
+
try:
|
297
|
+
# Determine config type from target
|
298
|
+
config_type = self._determine_config_type(target)
|
299
|
+
|
300
|
+
# Select configuration strategy
|
301
|
+
context = StrategyContext(
|
302
|
+
target_type=config_type,
|
303
|
+
operation="save",
|
304
|
+
parameters={"target": target},
|
305
|
+
)
|
306
|
+
|
307
|
+
strategy = self._registry.select_strategy(ConfigStrategy, context)
|
308
|
+
|
309
|
+
if not strategy:
|
310
|
+
return ConfigurationResult(
|
311
|
+
success=False,
|
312
|
+
validation_errors=[
|
313
|
+
f"No strategy available for config type: {config_type}"
|
314
|
+
],
|
315
|
+
)
|
316
|
+
|
317
|
+
# Validate configuration before saving
|
318
|
+
validation_errors = strategy.validate(config)
|
319
|
+
if validation_errors:
|
320
|
+
self._metrics["validation_errors"] += 1
|
321
|
+
return ConfigurationResult(
|
322
|
+
success=False,
|
323
|
+
config=config,
|
324
|
+
validation_errors=validation_errors,
|
325
|
+
)
|
326
|
+
|
327
|
+
# Transform configuration for saving
|
328
|
+
transformed_config = strategy.transform(config)
|
329
|
+
|
330
|
+
# Save configuration
|
331
|
+
self._logger.info(f"Saving configuration to {target}")
|
332
|
+
target_path = Path(target)
|
333
|
+
target_path.parent.mkdir(parents=True, exist_ok=True)
|
334
|
+
|
335
|
+
# Save based on file extension
|
336
|
+
if target_path.suffix == ".json":
|
337
|
+
with open(target_path, "w") as f:
|
338
|
+
json.dump(transformed_config, f, indent=2)
|
339
|
+
elif target_path.suffix in [".yaml", ".yml"]:
|
340
|
+
# Would use yaml library here
|
341
|
+
with open(target_path, "w") as f:
|
342
|
+
f.write(str(transformed_config))
|
343
|
+
else:
|
344
|
+
with open(target_path, "w") as f:
|
345
|
+
f.write(str(transformed_config))
|
346
|
+
|
347
|
+
return ConfigurationResult(
|
348
|
+
success=True,
|
349
|
+
config=transformed_config,
|
350
|
+
validation_errors=[],
|
351
|
+
source=str(target),
|
352
|
+
)
|
353
|
+
|
354
|
+
except Exception as e:
|
355
|
+
self._logger.error(f"Failed to save configuration: {str(e)}")
|
356
|
+
return ConfigurationResult(
|
357
|
+
success=False,
|
358
|
+
validation_errors=[f"Save failed: {str(e)}"],
|
359
|
+
)
|
360
|
+
|
361
|
+
def validate_config(self, config: Dict[str, Any]) -> List[str]:
|
362
|
+
"""
|
363
|
+
Validate configuration.
|
364
|
+
|
365
|
+
Args:
|
366
|
+
config: Configuration to validate
|
367
|
+
|
368
|
+
Returns:
|
369
|
+
List[str]: Validation errors
|
370
|
+
"""
|
371
|
+
errors = []
|
372
|
+
|
373
|
+
# Basic validation
|
374
|
+
if not config:
|
375
|
+
errors.append("Configuration is empty")
|
376
|
+
|
377
|
+
# Try to find matching strategy for validation
|
378
|
+
config_type = config.get("type", "generic")
|
379
|
+
context = StrategyContext(
|
380
|
+
target_type=config_type,
|
381
|
+
operation="validate",
|
382
|
+
)
|
383
|
+
|
384
|
+
strategy = self._registry.select_strategy(ConfigStrategy, context)
|
385
|
+
if strategy:
|
386
|
+
strategy_errors = strategy.validate(config)
|
387
|
+
errors.extend(strategy_errors)
|
388
|
+
else:
|
389
|
+
errors.append(f"No validation strategy for type: {config_type}")
|
390
|
+
|
391
|
+
return errors
|
392
|
+
|
393
|
+
def merge_configs(
|
394
|
+
self, *configs: Dict[str, Any], strategy: str = "deep"
|
395
|
+
) -> Dict[str, Any]:
|
396
|
+
"""
|
397
|
+
Merge multiple configurations.
|
398
|
+
|
399
|
+
Args:
|
400
|
+
*configs: Configurations to merge
|
401
|
+
strategy: Merge strategy
|
402
|
+
|
403
|
+
Returns:
|
404
|
+
Dict[str, Any]: Merged configuration
|
405
|
+
"""
|
406
|
+
self._metrics["merge_operations"] += 1
|
407
|
+
|
408
|
+
if not configs:
|
409
|
+
return {}
|
410
|
+
|
411
|
+
if len(configs) == 1:
|
412
|
+
return configs[0].copy()
|
413
|
+
|
414
|
+
# Implement merge based on strategy
|
415
|
+
if strategy == "deep":
|
416
|
+
return self._deep_merge(*configs)
|
417
|
+
elif strategy == "shallow":
|
418
|
+
return self._shallow_merge(*configs)
|
419
|
+
elif strategy == "override":
|
420
|
+
return self._override_merge(*configs)
|
421
|
+
else:
|
422
|
+
self._logger.warning(f"Unknown merge strategy: {strategy}, using deep")
|
423
|
+
return self._deep_merge(*configs)
|
424
|
+
|
425
|
+
def get_config_value(
|
426
|
+
self, key: str, default: Any = None, config: Optional[Dict[str, Any]] = None
|
427
|
+
) -> Any:
|
428
|
+
"""
|
429
|
+
Get configuration value by key.
|
430
|
+
|
431
|
+
Args:
|
432
|
+
key: Configuration key (dot notation)
|
433
|
+
default: Default value
|
434
|
+
config: Optional config dict
|
435
|
+
|
436
|
+
Returns:
|
437
|
+
Any: Configuration value
|
438
|
+
"""
|
439
|
+
if config is None:
|
440
|
+
# Use first loaded config as default
|
441
|
+
if self._configs:
|
442
|
+
config = next(iter(self._configs.values()))
|
443
|
+
else:
|
444
|
+
return default
|
445
|
+
|
446
|
+
# Support dot notation
|
447
|
+
keys = key.split(".")
|
448
|
+
value = config
|
449
|
+
|
450
|
+
for k in keys:
|
451
|
+
if isinstance(value, dict):
|
452
|
+
value = value.get(k)
|
453
|
+
if value is None:
|
454
|
+
return default
|
455
|
+
else:
|
456
|
+
return default
|
457
|
+
|
458
|
+
return value
|
459
|
+
|
460
|
+
def set_config_value(
|
461
|
+
self, key: str, value: Any, config: Optional[Dict[str, Any]] = None
|
462
|
+
) -> ConfigurationResult:
|
463
|
+
"""
|
464
|
+
Set configuration value by key.
|
465
|
+
|
466
|
+
Args:
|
467
|
+
key: Configuration key (dot notation)
|
468
|
+
value: Value to set
|
469
|
+
config: Optional config dict
|
470
|
+
|
471
|
+
Returns:
|
472
|
+
ConfigurationResult: Result of set operation
|
473
|
+
"""
|
474
|
+
if config is None:
|
475
|
+
if not self._configs:
|
476
|
+
return ConfigurationResult(
|
477
|
+
success=False,
|
478
|
+
validation_errors=["No configuration loaded"],
|
479
|
+
)
|
480
|
+
# Use first loaded config
|
481
|
+
config_id = next(iter(self._configs.keys()))
|
482
|
+
config = self._configs[config_id]
|
483
|
+
|
484
|
+
# Support dot notation
|
485
|
+
keys = key.split(".")
|
486
|
+
current = config
|
487
|
+
|
488
|
+
# Navigate to parent of target key
|
489
|
+
for k in keys[:-1]:
|
490
|
+
if k not in current:
|
491
|
+
current[k] = {}
|
492
|
+
current = current[k]
|
493
|
+
|
494
|
+
# Set the value
|
495
|
+
old_value = current.get(keys[-1])
|
496
|
+
current[keys[-1]] = value
|
497
|
+
|
498
|
+
# Validate updated configuration
|
499
|
+
validation_errors = self.validate_config(config)
|
500
|
+
|
501
|
+
if validation_errors:
|
502
|
+
# Rollback change
|
503
|
+
if old_value is None:
|
504
|
+
del current[keys[-1]]
|
505
|
+
else:
|
506
|
+
current[keys[-1]] = old_value
|
507
|
+
|
508
|
+
return ConfigurationResult(
|
509
|
+
success=False,
|
510
|
+
config=config,
|
511
|
+
validation_errors=validation_errors,
|
512
|
+
)
|
513
|
+
|
514
|
+
return ConfigurationResult(
|
515
|
+
success=True,
|
516
|
+
config=config,
|
517
|
+
validation_errors=[],
|
518
|
+
)
|
519
|
+
|
520
|
+
def get_schema(self) -> Dict[str, Any]:
|
521
|
+
"""
|
522
|
+
Get configuration schema.
|
523
|
+
|
524
|
+
Returns:
|
525
|
+
Dict[str, Any]: Configuration schema
|
526
|
+
"""
|
527
|
+
# Return first available schema or generic schema
|
528
|
+
if self._config_schemas:
|
529
|
+
return next(iter(self._config_schemas.values()))
|
530
|
+
|
531
|
+
# Return generic schema
|
532
|
+
return {
|
533
|
+
"type": "object",
|
534
|
+
"properties": {},
|
535
|
+
"additionalProperties": True,
|
536
|
+
}
|
537
|
+
|
538
|
+
def apply_defaults(self, config: Dict[str, Any]) -> Dict[str, Any]:
|
539
|
+
"""
|
540
|
+
Apply default values.
|
541
|
+
|
542
|
+
Args:
|
543
|
+
config: Configuration
|
544
|
+
|
545
|
+
Returns:
|
546
|
+
Dict[str, Any]: Configuration with defaults
|
547
|
+
"""
|
548
|
+
# Determine config type
|
549
|
+
config_type = config.get("type", "generic")
|
550
|
+
|
551
|
+
# Get appropriate strategy
|
552
|
+
context = StrategyContext(
|
553
|
+
target_type=config_type,
|
554
|
+
operation="defaults",
|
555
|
+
)
|
556
|
+
|
557
|
+
strategy = self._registry.select_strategy(ConfigStrategy, context)
|
558
|
+
if strategy:
|
559
|
+
return self._apply_strategy_defaults(strategy, config)
|
560
|
+
|
561
|
+
return config
|
562
|
+
|
563
|
+
# Private helper methods
|
564
|
+
|
565
|
+
def _register_default_strategies(self) -> None:
|
566
|
+
"""Register default configuration strategies."""
|
567
|
+
# Default strategies would be registered here
|
568
|
+
self._logger.debug("Default strategies registered")
|
569
|
+
|
570
|
+
async def _load_default_configs(self) -> None:
|
571
|
+
"""Load default configurations."""
|
572
|
+
# Implementation would load default configs
|
573
|
+
self._logger.debug("Default configurations loaded")
|
574
|
+
|
575
|
+
async def _save_all_configs(self) -> None:
|
576
|
+
"""Save all loaded configurations."""
|
577
|
+
# Implementation would save all configs
|
578
|
+
self._logger.debug("All configurations saved")
|
579
|
+
|
580
|
+
async def _start_hot_reload(self) -> None:
|
581
|
+
"""Start hot reload monitoring."""
|
582
|
+
self._logger.info("Hot reload started")
|
583
|
+
|
584
|
+
async def _stop_hot_reload(self) -> None:
|
585
|
+
"""Stop hot reload monitoring."""
|
586
|
+
self._logger.info("Hot reload stopped")
|
587
|
+
|
588
|
+
def _determine_config_type(self, source: Any) -> str:
|
589
|
+
"""Determine configuration type from source."""
|
590
|
+
if isinstance(source, dict):
|
591
|
+
return source.get("type", "generic")
|
592
|
+
|
593
|
+
if isinstance(source, (str, Path)):
|
594
|
+
path = Path(source)
|
595
|
+
# Infer from filename
|
596
|
+
if "project" in path.name.lower():
|
597
|
+
return "project"
|
598
|
+
elif "agent" in path.name.lower():
|
599
|
+
return "agent"
|
600
|
+
elif "env" in path.name.lower():
|
601
|
+
return "environment"
|
602
|
+
|
603
|
+
return "generic"
|
604
|
+
|
605
|
+
def _generate_config_id(self, source: Any, config_type: str) -> str:
|
606
|
+
"""Generate configuration ID."""
|
607
|
+
import hashlib
|
608
|
+
|
609
|
+
source_str = str(source)
|
610
|
+
return hashlib.md5(f"{config_type}:{source_str}".encode()).hexdigest()[:8]
|
611
|
+
|
612
|
+
def _apply_strategy_defaults(
|
613
|
+
self, strategy: ConfigStrategy, config: Dict[str, Any]
|
614
|
+
) -> Dict[str, Any]:
|
615
|
+
"""Apply defaults using strategy."""
|
616
|
+
schema = strategy.get_schema()
|
617
|
+
return self._apply_schema_defaults(config, schema)
|
618
|
+
|
619
|
+
def _apply_schema_defaults(
|
620
|
+
self, config: Dict[str, Any], schema: Dict[str, Any]
|
621
|
+
) -> Dict[str, Any]:
|
622
|
+
"""Apply defaults from schema."""
|
623
|
+
result = config.copy()
|
624
|
+
|
625
|
+
if "properties" in schema:
|
626
|
+
for key, prop_schema in schema["properties"].items():
|
627
|
+
if key not in result and "default" in prop_schema:
|
628
|
+
result[key] = prop_schema["default"]
|
629
|
+
|
630
|
+
return result
|
631
|
+
|
632
|
+
def _get_applied_defaults(
|
633
|
+
self, original: Dict[str, Any], with_defaults: Dict[str, Any]
|
634
|
+
) -> Dict[str, Any]:
|
635
|
+
"""Get dictionary of applied defaults."""
|
636
|
+
applied = {}
|
637
|
+
for key, value in with_defaults.items():
|
638
|
+
if key not in original:
|
639
|
+
applied[key] = value
|
640
|
+
return applied
|
641
|
+
|
642
|
+
def _add_to_version_history(
|
643
|
+
self, config_id: str, config: Dict[str, Any]
|
644
|
+
) -> None:
|
645
|
+
"""Add configuration to version history."""
|
646
|
+
if config_id not in self._config_versions:
|
647
|
+
self._config_versions[config_id] = []
|
648
|
+
|
649
|
+
version_entry = {
|
650
|
+
"timestamp": self._get_timestamp(),
|
651
|
+
"config": config.copy(),
|
652
|
+
}
|
653
|
+
|
654
|
+
self._config_versions[config_id].append(version_entry)
|
655
|
+
|
656
|
+
# Keep only last 10 versions
|
657
|
+
if len(self._config_versions[config_id]) > 10:
|
658
|
+
self._config_versions[config_id] = self._config_versions[config_id][-10:]
|
659
|
+
|
660
|
+
def _deep_merge(self, *configs: Dict[str, Any]) -> Dict[str, Any]:
|
661
|
+
"""Deep merge configurations."""
|
662
|
+
result = {}
|
663
|
+
|
664
|
+
for config in configs:
|
665
|
+
for key, value in config.items():
|
666
|
+
if key in result and isinstance(result[key], dict) and isinstance(value, dict):
|
667
|
+
result[key] = self._deep_merge(result[key], value)
|
668
|
+
else:
|
669
|
+
result[key] = value
|
670
|
+
|
671
|
+
return result
|
672
|
+
|
673
|
+
def _shallow_merge(self, *configs: Dict[str, Any]) -> Dict[str, Any]:
|
674
|
+
"""Shallow merge configurations."""
|
675
|
+
result = {}
|
676
|
+
for config in configs:
|
677
|
+
result.update(config)
|
678
|
+
return result
|
679
|
+
|
680
|
+
def _override_merge(self, *configs: Dict[str, Any]) -> Dict[str, Any]:
|
681
|
+
"""Override merge - last config wins."""
|
682
|
+
return configs[-1].copy() if configs else {}
|
683
|
+
|
684
|
+
def _get_timestamp(self) -> str:
|
685
|
+
"""Get current timestamp."""
|
686
|
+
from datetime import datetime
|
687
|
+
|
688
|
+
return datetime.now().isoformat()
|