claude-mpm 4.3.22__py3-none-any.whl → 4.4.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 (31) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/cli/commands/doctor.py +2 -2
  3. claude_mpm/hooks/memory_integration_hook.py +1 -1
  4. claude_mpm/services/agents/memory/content_manager.py +5 -2
  5. claude_mpm/services/agents/memory/memory_file_service.py +1 -0
  6. claude_mpm/services/agents/memory/memory_limits_service.py +1 -0
  7. claude_mpm/services/unified/__init__.py +65 -0
  8. claude_mpm/services/unified/analyzer_strategies/__init__.py +44 -0
  9. claude_mpm/services/unified/analyzer_strategies/code_analyzer.py +473 -0
  10. claude_mpm/services/unified/analyzer_strategies/dependency_analyzer.py +643 -0
  11. claude_mpm/services/unified/analyzer_strategies/performance_analyzer.py +804 -0
  12. claude_mpm/services/unified/analyzer_strategies/security_analyzer.py +661 -0
  13. claude_mpm/services/unified/analyzer_strategies/structure_analyzer.py +696 -0
  14. claude_mpm/services/unified/deployment_strategies/__init__.py +97 -0
  15. claude_mpm/services/unified/deployment_strategies/base.py +557 -0
  16. claude_mpm/services/unified/deployment_strategies/cloud_strategies.py +486 -0
  17. claude_mpm/services/unified/deployment_strategies/local.py +594 -0
  18. claude_mpm/services/unified/deployment_strategies/utils.py +672 -0
  19. claude_mpm/services/unified/deployment_strategies/vercel.py +471 -0
  20. claude_mpm/services/unified/interfaces.py +499 -0
  21. claude_mpm/services/unified/migration.py +532 -0
  22. claude_mpm/services/unified/strategies.py +551 -0
  23. claude_mpm/services/unified/unified_analyzer.py +534 -0
  24. claude_mpm/services/unified/unified_config.py +688 -0
  25. claude_mpm/services/unified/unified_deployment.py +470 -0
  26. {claude_mpm-4.3.22.dist-info → claude_mpm-4.4.0.dist-info}/METADATA +1 -1
  27. {claude_mpm-4.3.22.dist-info → claude_mpm-4.4.0.dist-info}/RECORD +31 -12
  28. {claude_mpm-4.3.22.dist-info → claude_mpm-4.4.0.dist-info}/WHEEL +0 -0
  29. {claude_mpm-4.3.22.dist-info → claude_mpm-4.4.0.dist-info}/entry_points.txt +0 -0
  30. {claude_mpm-4.3.22.dist-info → claude_mpm-4.4.0.dist-info}/licenses/LICENSE +0 -0
  31. {claude_mpm-4.3.22.dist-info → claude_mpm-4.4.0.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()