claude-mpm 4.0.23__py3-none-any.whl → 4.0.28__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 (60) hide show
  1. claude_mpm/BUILD_NUMBER +1 -1
  2. claude_mpm/VERSION +1 -1
  3. claude_mpm/agents/BASE_AGENT_TEMPLATE.md +4 -1
  4. claude_mpm/agents/BASE_PM.md +3 -0
  5. claude_mpm/agents/templates/code_analyzer.json +2 -2
  6. claude_mpm/cli/commands/agents.py +453 -113
  7. claude_mpm/cli/commands/aggregate.py +107 -15
  8. claude_mpm/cli/commands/cleanup.py +142 -10
  9. claude_mpm/cli/commands/config.py +358 -224
  10. claude_mpm/cli/commands/info.py +184 -75
  11. claude_mpm/cli/commands/mcp_command_router.py +5 -76
  12. claude_mpm/cli/commands/mcp_install_commands.py +68 -36
  13. claude_mpm/cli/commands/mcp_server_commands.py +30 -37
  14. claude_mpm/cli/commands/memory.py +331 -61
  15. claude_mpm/cli/commands/monitor.py +101 -7
  16. claude_mpm/cli/commands/run.py +368 -8
  17. claude_mpm/cli/commands/tickets.py +206 -24
  18. claude_mpm/cli/parsers/mcp_parser.py +3 -0
  19. claude_mpm/cli/shared/__init__.py +40 -0
  20. claude_mpm/cli/shared/argument_patterns.py +212 -0
  21. claude_mpm/cli/shared/command_base.py +234 -0
  22. claude_mpm/cli/shared/error_handling.py +238 -0
  23. claude_mpm/cli/shared/output_formatters.py +231 -0
  24. claude_mpm/config/agent_config.py +29 -8
  25. claude_mpm/core/container.py +6 -4
  26. claude_mpm/core/service_registry.py +4 -2
  27. claude_mpm/core/shared/__init__.py +17 -0
  28. claude_mpm/core/shared/config_loader.py +320 -0
  29. claude_mpm/core/shared/path_resolver.py +277 -0
  30. claude_mpm/core/shared/singleton_manager.py +208 -0
  31. claude_mpm/hooks/claude_hooks/memory_integration.py +4 -2
  32. claude_mpm/hooks/claude_hooks/response_tracking.py +14 -3
  33. claude_mpm/hooks/memory_integration_hook.py +11 -2
  34. claude_mpm/services/agents/deployment/agent_deployment.py +49 -23
  35. claude_mpm/services/agents/deployment/deployment_wrapper.py +71 -0
  36. claude_mpm/services/agents/deployment/pipeline/pipeline_context.py +1 -0
  37. claude_mpm/services/agents/deployment/pipeline/steps/agent_processing_step.py +43 -0
  38. claude_mpm/services/agents/deployment/processors/agent_deployment_context.py +4 -0
  39. claude_mpm/services/agents/deployment/processors/agent_processor.py +1 -1
  40. claude_mpm/services/agents/loading/base_agent_manager.py +11 -3
  41. claude_mpm/services/agents/registry/deployed_agent_discovery.py +14 -5
  42. claude_mpm/services/event_aggregator.py +4 -2
  43. claude_mpm/services/mcp_gateway/config/config_loader.py +89 -28
  44. claude_mpm/services/mcp_gateway/config/configuration.py +29 -0
  45. claude_mpm/services/mcp_gateway/registry/service_registry.py +22 -5
  46. claude_mpm/services/memory/builder.py +6 -1
  47. claude_mpm/services/response_tracker.py +3 -1
  48. claude_mpm/services/runner_configuration_service.py +15 -6
  49. claude_mpm/services/shared/__init__.py +20 -0
  50. claude_mpm/services/shared/async_service_base.py +219 -0
  51. claude_mpm/services/shared/config_service_base.py +292 -0
  52. claude_mpm/services/shared/lifecycle_service_base.py +317 -0
  53. claude_mpm/services/shared/manager_base.py +303 -0
  54. claude_mpm/services/shared/service_factory.py +308 -0
  55. {claude_mpm-4.0.23.dist-info → claude_mpm-4.0.28.dist-info}/METADATA +19 -13
  56. {claude_mpm-4.0.23.dist-info → claude_mpm-4.0.28.dist-info}/RECORD +60 -44
  57. {claude_mpm-4.0.23.dist-info → claude_mpm-4.0.28.dist-info}/WHEEL +0 -0
  58. {claude_mpm-4.0.23.dist-info → claude_mpm-4.0.28.dist-info}/entry_points.txt +0 -0
  59. {claude_mpm-4.0.23.dist-info → claude_mpm-4.0.28.dist-info}/licenses/LICENSE +0 -0
  60. {claude_mpm-4.0.23.dist-info → claude_mpm-4.0.28.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,317 @@
1
+ """
2
+ Base class for services with complex lifecycle management.
3
+ """
4
+
5
+ import time
6
+ from abc import ABC, abstractmethod
7
+ from enum import Enum
8
+ from typing import Any, Dict, List, Optional
9
+
10
+ from ...core.mixins import LoggerMixin
11
+
12
+
13
+ class ServiceState(Enum):
14
+ """Standard service states."""
15
+ UNINITIALIZED = "uninitialized"
16
+ INITIALIZING = "initializing"
17
+ INITIALIZED = "initialized"
18
+ STARTING = "starting"
19
+ RUNNING = "running"
20
+ STOPPING = "stopping"
21
+ STOPPED = "stopped"
22
+ ERROR = "error"
23
+
24
+
25
+ class LifecycleServiceBase(LoggerMixin, ABC):
26
+ """
27
+ Base class for services with complex lifecycle management.
28
+
29
+ Provides common patterns:
30
+ - State machine management
31
+ - Lifecycle hooks
32
+ - Health monitoring
33
+ - Dependency management
34
+ """
35
+
36
+ def __init__(self, service_name: str, config: Optional[Dict[str, Any]] = None):
37
+ """
38
+ Initialize lifecycle service.
39
+
40
+ Args:
41
+ service_name: Name of the service
42
+ config: Optional configuration
43
+ """
44
+ self.service_name = service_name
45
+ self._logger_name = f"service.{service_name}"
46
+ self.config = config or {}
47
+
48
+ # State management
49
+ self._state = ServiceState.UNINITIALIZED
50
+ self._previous_state = None
51
+ self._state_history: List[tuple] = []
52
+
53
+ # Lifecycle tracking
54
+ self._start_time: Optional[float] = None
55
+ self._last_health_check: Optional[float] = None
56
+ self._health_status: Dict[str, Any] = {}
57
+
58
+ # Dependencies
59
+ self._dependencies: List[str] = []
60
+ self._dependents: List[str] = []
61
+
62
+ # Error tracking
63
+ self._errors: List[Dict[str, Any]] = []
64
+ self._max_errors = 10
65
+
66
+ @property
67
+ def state(self) -> ServiceState:
68
+ """Get current service state."""
69
+ return self._state
70
+
71
+ @property
72
+ def is_running(self) -> bool:
73
+ """Check if service is running."""
74
+ return self._state == ServiceState.RUNNING
75
+
76
+ @property
77
+ def is_healthy(self) -> bool:
78
+ """Check if service is healthy."""
79
+ return (
80
+ self._state == ServiceState.RUNNING and
81
+ len(self._errors) == 0 and
82
+ self._health_status.get('healthy', True)
83
+ )
84
+
85
+ @property
86
+ def uptime(self) -> Optional[float]:
87
+ """Get service uptime in seconds."""
88
+ if self._start_time and self.is_running:
89
+ return time.time() - self._start_time
90
+ return None
91
+
92
+ def add_dependency(self, service_name: str) -> None:
93
+ """Add a service dependency."""
94
+ if service_name not in self._dependencies:
95
+ self._dependencies.append(service_name)
96
+ self.logger.debug(f"Added dependency: {service_name}")
97
+
98
+ def add_dependent(self, service_name: str) -> None:
99
+ """Add a dependent service."""
100
+ if service_name not in self._dependents:
101
+ self._dependents.append(service_name)
102
+ self.logger.debug(f"Added dependent: {service_name}")
103
+
104
+ def initialize(self) -> bool:
105
+ """
106
+ Initialize the service.
107
+
108
+ Returns:
109
+ True if initialization successful
110
+ """
111
+ if self._state != ServiceState.UNINITIALIZED:
112
+ self.logger.warning(f"Service {self.service_name} already initialized")
113
+ return self._state in (ServiceState.INITIALIZED, ServiceState.RUNNING)
114
+
115
+ self._transition_state(ServiceState.INITIALIZING)
116
+ self.logger.info(f"Initializing service: {self.service_name}")
117
+
118
+ try:
119
+ # Check dependencies
120
+ if not self._check_dependencies():
121
+ self._transition_state(ServiceState.ERROR)
122
+ return False
123
+
124
+ # Service-specific initialization
125
+ success = self._do_initialize()
126
+
127
+ if success:
128
+ self._transition_state(ServiceState.INITIALIZED)
129
+ self.logger.info(f"Service {self.service_name} initialized successfully")
130
+ else:
131
+ self._transition_state(ServiceState.ERROR)
132
+ self.logger.error(f"Service {self.service_name} initialization failed")
133
+
134
+ return success
135
+
136
+ except Exception as e:
137
+ self._record_error("initialization", e)
138
+ self._transition_state(ServiceState.ERROR)
139
+ self.logger.error(f"Service {self.service_name} initialization error: {e}", exc_info=True)
140
+ return False
141
+
142
+ def start(self) -> bool:
143
+ """
144
+ Start the service.
145
+
146
+ Returns:
147
+ True if start successful
148
+ """
149
+ if self._state == ServiceState.RUNNING:
150
+ self.logger.warning(f"Service {self.service_name} already running")
151
+ return True
152
+
153
+ if self._state != ServiceState.INITIALIZED:
154
+ if not self.initialize():
155
+ return False
156
+
157
+ self._transition_state(ServiceState.STARTING)
158
+ self.logger.info(f"Starting service: {self.service_name}")
159
+
160
+ try:
161
+ success = self._do_start()
162
+
163
+ if success:
164
+ self._start_time = time.time()
165
+ self._transition_state(ServiceState.RUNNING)
166
+ self.logger.info(f"Service {self.service_name} started successfully")
167
+ else:
168
+ self._transition_state(ServiceState.ERROR)
169
+ self.logger.error(f"Service {self.service_name} start failed")
170
+
171
+ return success
172
+
173
+ except Exception as e:
174
+ self._record_error("start", e)
175
+ self._transition_state(ServiceState.ERROR)
176
+ self.logger.error(f"Service {self.service_name} start error: {e}", exc_info=True)
177
+ return False
178
+
179
+ def stop(self) -> bool:
180
+ """
181
+ Stop the service.
182
+
183
+ Returns:
184
+ True if stop successful
185
+ """
186
+ if self._state == ServiceState.STOPPED:
187
+ self.logger.warning(f"Service {self.service_name} already stopped")
188
+ return True
189
+
190
+ self._transition_state(ServiceState.STOPPING)
191
+ self.logger.info(f"Stopping service: {self.service_name}")
192
+
193
+ try:
194
+ success = self._do_stop()
195
+
196
+ if success:
197
+ self._start_time = None
198
+ self._transition_state(ServiceState.STOPPED)
199
+ self.logger.info(f"Service {self.service_name} stopped successfully")
200
+ else:
201
+ self._transition_state(ServiceState.ERROR)
202
+ self.logger.error(f"Service {self.service_name} stop failed")
203
+
204
+ return success
205
+
206
+ except Exception as e:
207
+ self._record_error("stop", e)
208
+ self._transition_state(ServiceState.ERROR)
209
+ self.logger.error(f"Service {self.service_name} stop error: {e}", exc_info=True)
210
+ return False
211
+
212
+ def restart(self) -> bool:
213
+ """Restart the service."""
214
+ self.logger.info(f"Restarting service: {self.service_name}")
215
+
216
+ if self.is_running:
217
+ if not self.stop():
218
+ return False
219
+
220
+ return self.start()
221
+
222
+ def health_check(self) -> Dict[str, Any]:
223
+ """
224
+ Perform health check.
225
+
226
+ Returns:
227
+ Health status dictionary
228
+ """
229
+ self._last_health_check = time.time()
230
+
231
+ try:
232
+ # Service-specific health check
233
+ service_health = self._do_health_check()
234
+
235
+ # Combine with base health info
236
+ health_status = {
237
+ "service": self.service_name,
238
+ "state": self._state.value,
239
+ "healthy": self.is_healthy,
240
+ "uptime": self.uptime,
241
+ "error_count": len(self._errors),
242
+ "dependencies": self._dependencies,
243
+ "dependents": self._dependents,
244
+ "last_check": self._last_health_check,
245
+ **service_health
246
+ }
247
+
248
+ self._health_status = health_status
249
+ return health_status
250
+
251
+ except Exception as e:
252
+ self._record_error("health_check", e)
253
+ return {
254
+ "service": self.service_name,
255
+ "state": self._state.value,
256
+ "healthy": False,
257
+ "error": str(e)
258
+ }
259
+
260
+ def _transition_state(self, new_state: ServiceState) -> None:
261
+ """Transition to a new state."""
262
+ old_state = self._state
263
+ self._previous_state = old_state
264
+ self._state = new_state
265
+
266
+ # Record state transition
267
+ self._state_history.append((time.time(), old_state.value, new_state.value))
268
+
269
+ # Keep history bounded
270
+ if len(self._state_history) > 50:
271
+ self._state_history = self._state_history[-50:]
272
+
273
+ self.logger.debug(f"State transition: {old_state.value} -> {new_state.value}")
274
+
275
+ def _record_error(self, operation: str, error: Exception) -> None:
276
+ """Record an error."""
277
+ error_record = {
278
+ "timestamp": time.time(),
279
+ "operation": operation,
280
+ "error": str(error),
281
+ "type": type(error).__name__
282
+ }
283
+
284
+ self._errors.append(error_record)
285
+
286
+ # Keep error list bounded
287
+ if len(self._errors) > self._max_errors:
288
+ self._errors = self._errors[-self._max_errors:]
289
+
290
+ def _check_dependencies(self) -> bool:
291
+ """Check if all dependencies are satisfied."""
292
+ # This is a placeholder - in a real implementation,
293
+ # you would check with a service registry
294
+ return True
295
+
296
+ @abstractmethod
297
+ def _do_initialize(self) -> bool:
298
+ """Service-specific initialization logic."""
299
+ pass
300
+
301
+ @abstractmethod
302
+ def _do_start(self) -> bool:
303
+ """Service-specific start logic."""
304
+ pass
305
+
306
+ @abstractmethod
307
+ def _do_stop(self) -> bool:
308
+ """Service-specific stop logic."""
309
+ pass
310
+
311
+ def _do_health_check(self) -> Dict[str, Any]:
312
+ """Service-specific health check logic."""
313
+ return {"healthy": True}
314
+
315
+ def __repr__(self) -> str:
316
+ """String representation."""
317
+ return f"{self.__class__.__name__}(name={self.service_name}, state={self._state.value})"
@@ -0,0 +1,303 @@
1
+ """
2
+ Base class for manager-style services to reduce duplication.
3
+ """
4
+
5
+ from abc import ABC, abstractmethod
6
+ from typing import Any, Dict, List, Optional, Set, TypeVar
7
+
8
+ from ...core.mixins import LoggerMixin
9
+ from .config_service_base import ConfigServiceBase
10
+
11
+ T = TypeVar('T')
12
+
13
+
14
+ class ManagerBase(ConfigServiceBase, ABC):
15
+ """
16
+ Base class for manager-style services.
17
+
18
+ Provides common patterns for services that manage collections of items:
19
+ - Item registration and discovery
20
+ - Lifecycle management
21
+ - Caching and indexing
22
+ - Validation and filtering
23
+ """
24
+
25
+ def __init__(self,
26
+ manager_name: str,
27
+ config: Optional[Dict[str, Any]] = None,
28
+ config_section: Optional[str] = None):
29
+ """
30
+ Initialize manager.
31
+
32
+ Args:
33
+ manager_name: Name of the manager
34
+ config: Configuration dictionary
35
+ config_section: Configuration section name
36
+ """
37
+ super().__init__(manager_name, config, config_section)
38
+
39
+ # Item storage
40
+ self._items: Dict[str, T] = {}
41
+ self._item_metadata: Dict[str, Dict[str, Any]] = {}
42
+
43
+ # Indexing
44
+ self._indexes: Dict[str, Dict[Any, Set[str]]] = {}
45
+
46
+ # State tracking
47
+ self._initialized = False
48
+ self._last_scan_time: Optional[float] = None
49
+
50
+ # Configuration
51
+ self._auto_scan = self.get_config_value("auto_scan", True, config_type=bool)
52
+ self._cache_enabled = self.get_config_value("cache_enabled", True, config_type=bool)
53
+ self._max_items = self.get_config_value("max_items", 1000, config_type=int)
54
+
55
+ @property
56
+ def item_count(self) -> int:
57
+ """Get number of managed items."""
58
+ return len(self._items)
59
+
60
+ @property
61
+ def is_initialized(self) -> bool:
62
+ """Check if manager is initialized."""
63
+ return self._initialized
64
+
65
+ def initialize(self) -> bool:
66
+ """
67
+ Initialize the manager.
68
+
69
+ Returns:
70
+ True if initialization successful
71
+ """
72
+ if self._initialized:
73
+ self.logger.warning(f"Manager {self.service_name} already initialized")
74
+ return True
75
+
76
+ self.logger.info(f"Initializing manager: {self.service_name}")
77
+
78
+ try:
79
+ # Manager-specific initialization
80
+ success = self._do_initialize()
81
+
82
+ if success:
83
+ # Perform initial scan if auto-scan enabled
84
+ if self._auto_scan:
85
+ self.scan_items()
86
+
87
+ self._initialized = True
88
+ self.logger.info(f"Manager {self.service_name} initialized successfully")
89
+ else:
90
+ self.logger.error(f"Manager {self.service_name} initialization failed")
91
+
92
+ return success
93
+
94
+ except Exception as e:
95
+ self.logger.error(f"Manager {self.service_name} initialization error: {e}", exc_info=True)
96
+ return False
97
+
98
+ def register_item(self, item_id: str, item: T, metadata: Dict[str, Any] = None) -> bool:
99
+ """
100
+ Register an item.
101
+
102
+ Args:
103
+ item_id: Unique identifier for the item
104
+ item: The item to register
105
+ metadata: Optional metadata for the item
106
+
107
+ Returns:
108
+ True if registration successful
109
+ """
110
+ if not self._initialized:
111
+ self.logger.warning("Manager not initialized")
112
+ return False
113
+
114
+ # Check item limit
115
+ if len(self._items) >= self._max_items:
116
+ self.logger.warning(f"Maximum items ({self._max_items}) reached")
117
+ return False
118
+
119
+ # Validate item
120
+ if not self._validate_item(item_id, item):
121
+ return False
122
+
123
+ # Store item
124
+ self._items[item_id] = item
125
+ self._item_metadata[item_id] = metadata or {}
126
+
127
+ # Update indexes
128
+ self._update_indexes(item_id, item, metadata)
129
+
130
+ self.logger.debug(f"Registered item: {item_id}")
131
+ return True
132
+
133
+ def unregister_item(self, item_id: str) -> bool:
134
+ """
135
+ Unregister an item.
136
+
137
+ Args:
138
+ item_id: Identifier of item to unregister
139
+
140
+ Returns:
141
+ True if unregistration successful
142
+ """
143
+ if item_id not in self._items:
144
+ self.logger.warning(f"Item not found: {item_id}")
145
+ return False
146
+
147
+ # Remove from storage
148
+ item = self._items.pop(item_id)
149
+ metadata = self._item_metadata.pop(item_id, {})
150
+
151
+ # Update indexes
152
+ self._remove_from_indexes(item_id, item, metadata)
153
+
154
+ self.logger.debug(f"Unregistered item: {item_id}")
155
+ return True
156
+
157
+ def get_item(self, item_id: str) -> Optional[T]:
158
+ """Get item by ID."""
159
+ return self._items.get(item_id)
160
+
161
+ def get_item_metadata(self, item_id: str) -> Dict[str, Any]:
162
+ """Get item metadata."""
163
+ return self._item_metadata.get(item_id, {})
164
+
165
+ def list_items(self, filter_func=None) -> List[str]:
166
+ """
167
+ List item IDs.
168
+
169
+ Args:
170
+ filter_func: Optional filter function
171
+
172
+ Returns:
173
+ List of item IDs
174
+ """
175
+ if filter_func is None:
176
+ return list(self._items.keys())
177
+
178
+ return [
179
+ item_id for item_id, item in self._items.items()
180
+ if filter_func(item_id, item, self._item_metadata.get(item_id, {}))
181
+ ]
182
+
183
+ def find_items(self, **criteria) -> List[str]:
184
+ """
185
+ Find items by criteria using indexes.
186
+
187
+ Args:
188
+ **criteria: Search criteria
189
+
190
+ Returns:
191
+ List of matching item IDs
192
+ """
193
+ if not criteria:
194
+ return self.list_items()
195
+
196
+ # Use indexes if available
197
+ result_sets = []
198
+ for key, value in criteria.items():
199
+ if key in self._indexes and value in self._indexes[key]:
200
+ result_sets.append(self._indexes[key][value])
201
+
202
+ if result_sets:
203
+ # Intersection of all criteria
204
+ result = result_sets[0]
205
+ for result_set in result_sets[1:]:
206
+ result = result.intersection(result_set)
207
+ return list(result)
208
+
209
+ # Fallback to linear search
210
+ return self.list_items(
211
+ lambda item_id, item, metadata: all(
212
+ metadata.get(key) == value for key, value in criteria.items()
213
+ )
214
+ )
215
+
216
+ def scan_items(self) -> int:
217
+ """
218
+ Scan for items and update registry.
219
+
220
+ Returns:
221
+ Number of items found
222
+ """
223
+ import time
224
+
225
+ self.logger.info(f"Scanning for items: {self.service_name}")
226
+ start_time = time.time()
227
+
228
+ try:
229
+ # Manager-specific scanning
230
+ found_items = self._do_scan_items()
231
+
232
+ # Update scan time
233
+ self._last_scan_time = time.time()
234
+
235
+ scan_duration = self._last_scan_time - start_time
236
+ self.logger.info(f"Scan completed: found {found_items} items in {scan_duration:.2f}s")
237
+
238
+ return found_items
239
+
240
+ except Exception as e:
241
+ self.logger.error(f"Scan failed: {e}", exc_info=True)
242
+ return 0
243
+
244
+ def clear_items(self) -> None:
245
+ """Clear all items."""
246
+ self.logger.info(f"Clearing all items: {self.service_name}")
247
+ self._items.clear()
248
+ self._item_metadata.clear()
249
+ self._indexes.clear()
250
+
251
+ def get_status(self) -> Dict[str, Any]:
252
+ """
253
+ Get manager status.
254
+
255
+ Returns:
256
+ Status dictionary
257
+ """
258
+ return {
259
+ "manager": self.service_name,
260
+ "initialized": self._initialized,
261
+ "item_count": self.item_count,
262
+ "max_items": self._max_items,
263
+ "auto_scan": self._auto_scan,
264
+ "cache_enabled": self._cache_enabled,
265
+ "last_scan_time": self._last_scan_time,
266
+ "indexes": list(self._indexes.keys())
267
+ }
268
+
269
+ def _create_index(self, index_name: str) -> None:
270
+ """Create an index for fast lookups."""
271
+ if index_name not in self._indexes:
272
+ self._indexes[index_name] = {}
273
+ self.logger.debug(f"Created index: {index_name}")
274
+
275
+ def _update_indexes(self, item_id: str, item: T, metadata: Dict[str, Any]) -> None:
276
+ """Update indexes for an item."""
277
+ # Manager-specific index updates
278
+ self._do_update_indexes(item_id, item, metadata)
279
+
280
+ def _remove_from_indexes(self, item_id: str, item: T, metadata: Dict[str, Any]) -> None:
281
+ """Remove item from indexes."""
282
+ for index_name, index in self._indexes.items():
283
+ for value, item_set in index.items():
284
+ item_set.discard(item_id)
285
+
286
+ @abstractmethod
287
+ def _do_initialize(self) -> bool:
288
+ """Manager-specific initialization logic."""
289
+ pass
290
+
291
+ @abstractmethod
292
+ def _validate_item(self, item_id: str, item: T) -> bool:
293
+ """Validate an item before registration."""
294
+ pass
295
+
296
+ @abstractmethod
297
+ def _do_scan_items(self) -> int:
298
+ """Manager-specific item scanning logic."""
299
+ pass
300
+
301
+ def _do_update_indexes(self, item_id: str, item: T, metadata: Dict[str, Any]) -> None:
302
+ """Manager-specific index update logic."""
303
+ pass