crackerjack 0.31.10__py3-none-any.whl → 0.31.12__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 crackerjack might be problematic. Click here for more details.

Files changed (155) hide show
  1. crackerjack/CLAUDE.md +288 -705
  2. crackerjack/__main__.py +22 -8
  3. crackerjack/agents/__init__.py +0 -3
  4. crackerjack/agents/architect_agent.py +0 -43
  5. crackerjack/agents/base.py +1 -9
  6. crackerjack/agents/coordinator.py +2 -148
  7. crackerjack/agents/documentation_agent.py +109 -81
  8. crackerjack/agents/dry_agent.py +122 -97
  9. crackerjack/agents/formatting_agent.py +3 -16
  10. crackerjack/agents/import_optimization_agent.py +1174 -130
  11. crackerjack/agents/performance_agent.py +956 -188
  12. crackerjack/agents/performance_helpers.py +229 -0
  13. crackerjack/agents/proactive_agent.py +1 -48
  14. crackerjack/agents/refactoring_agent.py +516 -246
  15. crackerjack/agents/refactoring_helpers.py +282 -0
  16. crackerjack/agents/security_agent.py +393 -90
  17. crackerjack/agents/test_creation_agent.py +1776 -120
  18. crackerjack/agents/test_specialist_agent.py +59 -15
  19. crackerjack/agents/tracker.py +0 -102
  20. crackerjack/api.py +145 -37
  21. crackerjack/cli/handlers.py +48 -30
  22. crackerjack/cli/interactive.py +11 -11
  23. crackerjack/cli/options.py +66 -4
  24. crackerjack/code_cleaner.py +808 -148
  25. crackerjack/config/global_lock_config.py +110 -0
  26. crackerjack/config/hooks.py +43 -64
  27. crackerjack/core/async_workflow_orchestrator.py +247 -97
  28. crackerjack/core/autofix_coordinator.py +192 -109
  29. crackerjack/core/enhanced_container.py +46 -63
  30. crackerjack/core/file_lifecycle.py +549 -0
  31. crackerjack/core/performance.py +9 -8
  32. crackerjack/core/performance_monitor.py +395 -0
  33. crackerjack/core/phase_coordinator.py +281 -94
  34. crackerjack/core/proactive_workflow.py +9 -58
  35. crackerjack/core/resource_manager.py +501 -0
  36. crackerjack/core/service_watchdog.py +490 -0
  37. crackerjack/core/session_coordinator.py +4 -8
  38. crackerjack/core/timeout_manager.py +504 -0
  39. crackerjack/core/websocket_lifecycle.py +475 -0
  40. crackerjack/core/workflow_orchestrator.py +343 -209
  41. crackerjack/dynamic_config.py +47 -6
  42. crackerjack/errors.py +3 -4
  43. crackerjack/executors/async_hook_executor.py +63 -13
  44. crackerjack/executors/cached_hook_executor.py +14 -14
  45. crackerjack/executors/hook_executor.py +100 -37
  46. crackerjack/executors/hook_lock_manager.py +856 -0
  47. crackerjack/executors/individual_hook_executor.py +120 -86
  48. crackerjack/intelligence/__init__.py +0 -7
  49. crackerjack/intelligence/adaptive_learning.py +13 -86
  50. crackerjack/intelligence/agent_orchestrator.py +15 -78
  51. crackerjack/intelligence/agent_registry.py +12 -59
  52. crackerjack/intelligence/agent_selector.py +31 -92
  53. crackerjack/intelligence/integration.py +1 -41
  54. crackerjack/interactive.py +9 -9
  55. crackerjack/managers/async_hook_manager.py +25 -8
  56. crackerjack/managers/hook_manager.py +9 -9
  57. crackerjack/managers/publish_manager.py +57 -59
  58. crackerjack/managers/test_command_builder.py +6 -36
  59. crackerjack/managers/test_executor.py +9 -61
  60. crackerjack/managers/test_manager.py +17 -63
  61. crackerjack/managers/test_manager_backup.py +77 -127
  62. crackerjack/managers/test_progress.py +4 -23
  63. crackerjack/mcp/cache.py +5 -12
  64. crackerjack/mcp/client_runner.py +10 -10
  65. crackerjack/mcp/context.py +64 -6
  66. crackerjack/mcp/dashboard.py +14 -11
  67. crackerjack/mcp/enhanced_progress_monitor.py +55 -55
  68. crackerjack/mcp/file_monitor.py +72 -42
  69. crackerjack/mcp/progress_components.py +103 -84
  70. crackerjack/mcp/progress_monitor.py +122 -49
  71. crackerjack/mcp/rate_limiter.py +12 -12
  72. crackerjack/mcp/server_core.py +16 -22
  73. crackerjack/mcp/service_watchdog.py +26 -26
  74. crackerjack/mcp/state.py +15 -0
  75. crackerjack/mcp/tools/core_tools.py +95 -39
  76. crackerjack/mcp/tools/error_analyzer.py +6 -32
  77. crackerjack/mcp/tools/execution_tools.py +1 -56
  78. crackerjack/mcp/tools/execution_tools_backup.py +35 -131
  79. crackerjack/mcp/tools/intelligence_tool_registry.py +0 -36
  80. crackerjack/mcp/tools/intelligence_tools.py +2 -55
  81. crackerjack/mcp/tools/monitoring_tools.py +308 -145
  82. crackerjack/mcp/tools/proactive_tools.py +12 -42
  83. crackerjack/mcp/tools/progress_tools.py +23 -15
  84. crackerjack/mcp/tools/utility_tools.py +3 -40
  85. crackerjack/mcp/tools/workflow_executor.py +40 -60
  86. crackerjack/mcp/websocket/app.py +0 -3
  87. crackerjack/mcp/websocket/endpoints.py +206 -268
  88. crackerjack/mcp/websocket/jobs.py +213 -66
  89. crackerjack/mcp/websocket/server.py +84 -6
  90. crackerjack/mcp/websocket/websocket_handler.py +137 -29
  91. crackerjack/models/config_adapter.py +3 -16
  92. crackerjack/models/protocols.py +162 -3
  93. crackerjack/models/resource_protocols.py +454 -0
  94. crackerjack/models/task.py +3 -3
  95. crackerjack/monitoring/__init__.py +0 -0
  96. crackerjack/monitoring/ai_agent_watchdog.py +25 -71
  97. crackerjack/monitoring/regression_prevention.py +28 -87
  98. crackerjack/orchestration/advanced_orchestrator.py +44 -78
  99. crackerjack/orchestration/coverage_improvement.py +10 -60
  100. crackerjack/orchestration/execution_strategies.py +16 -16
  101. crackerjack/orchestration/test_progress_streamer.py +61 -53
  102. crackerjack/plugins/base.py +1 -1
  103. crackerjack/plugins/managers.py +22 -20
  104. crackerjack/py313.py +65 -21
  105. crackerjack/services/backup_service.py +467 -0
  106. crackerjack/services/bounded_status_operations.py +627 -0
  107. crackerjack/services/cache.py +7 -9
  108. crackerjack/services/config.py +35 -52
  109. crackerjack/services/config_integrity.py +5 -16
  110. crackerjack/services/config_merge.py +542 -0
  111. crackerjack/services/contextual_ai_assistant.py +17 -19
  112. crackerjack/services/coverage_ratchet.py +44 -73
  113. crackerjack/services/debug.py +25 -39
  114. crackerjack/services/dependency_monitor.py +52 -50
  115. crackerjack/services/enhanced_filesystem.py +14 -11
  116. crackerjack/services/file_hasher.py +1 -1
  117. crackerjack/services/filesystem.py +1 -12
  118. crackerjack/services/git.py +71 -47
  119. crackerjack/services/health_metrics.py +31 -27
  120. crackerjack/services/initialization.py +276 -428
  121. crackerjack/services/input_validator.py +760 -0
  122. crackerjack/services/log_manager.py +16 -16
  123. crackerjack/services/logging.py +7 -6
  124. crackerjack/services/metrics.py +43 -43
  125. crackerjack/services/pattern_cache.py +2 -31
  126. crackerjack/services/pattern_detector.py +26 -63
  127. crackerjack/services/performance_benchmarks.py +20 -45
  128. crackerjack/services/regex_patterns.py +2887 -0
  129. crackerjack/services/regex_utils.py +537 -0
  130. crackerjack/services/secure_path_utils.py +683 -0
  131. crackerjack/services/secure_status_formatter.py +534 -0
  132. crackerjack/services/secure_subprocess.py +605 -0
  133. crackerjack/services/security.py +47 -10
  134. crackerjack/services/security_logger.py +492 -0
  135. crackerjack/services/server_manager.py +109 -50
  136. crackerjack/services/smart_scheduling.py +8 -25
  137. crackerjack/services/status_authentication.py +603 -0
  138. crackerjack/services/status_security_manager.py +442 -0
  139. crackerjack/services/thread_safe_status_collector.py +546 -0
  140. crackerjack/services/tool_version_service.py +1 -23
  141. crackerjack/services/unified_config.py +36 -58
  142. crackerjack/services/validation_rate_limiter.py +269 -0
  143. crackerjack/services/version_checker.py +9 -40
  144. crackerjack/services/websocket_resource_limiter.py +572 -0
  145. crackerjack/slash_commands/__init__.py +52 -2
  146. crackerjack/tools/__init__.py +0 -0
  147. crackerjack/tools/validate_input_validator_patterns.py +262 -0
  148. crackerjack/tools/validate_regex_patterns.py +198 -0
  149. {crackerjack-0.31.10.dist-info → crackerjack-0.31.12.dist-info}/METADATA +197 -12
  150. crackerjack-0.31.12.dist-info/RECORD +178 -0
  151. crackerjack/cli/facade.py +0 -104
  152. crackerjack-0.31.10.dist-info/RECORD +0 -149
  153. {crackerjack-0.31.10.dist-info → crackerjack-0.31.12.dist-info}/WHEEL +0 -0
  154. {crackerjack-0.31.10.dist-info → crackerjack-0.31.12.dist-info}/entry_points.txt +0 -0
  155. {crackerjack-0.31.10.dist-info → crackerjack-0.31.12.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,501 @@
1
+ """Comprehensive resource management with automatic cleanup patterns.
2
+
3
+ This module provides RAII (Resource Acquisition Is Initialization) patterns
4
+ and comprehensive resource lifecycle management to prevent resource leaks
5
+ even in error scenarios.
6
+ """
7
+
8
+ import asyncio
9
+ import contextlib
10
+ import logging
11
+ import tempfile
12
+ import threading
13
+ import time
14
+ import typing as t
15
+ import weakref
16
+ from abc import ABC, abstractmethod
17
+ from pathlib import Path
18
+ from types import TracebackType
19
+
20
+
21
+ class ResourceProtocol(t.Protocol):
22
+ """Protocol for resources that can be cleaned up."""
23
+
24
+ async def cleanup(self) -> None:
25
+ """Clean up the resource."""
26
+ ...
27
+
28
+
29
+ class ResourceManager:
30
+ """Centralized resource management with automatic cleanup on error scenarios."""
31
+
32
+ def __init__(self, logger: logging.Logger | None = None) -> None:
33
+ self.logger = logger or logging.getLogger(__name__)
34
+ self._resources: list[ResourceProtocol] = []
35
+ self._cleanup_callbacks: list[t.Callable[[], t.Awaitable[None]]] = []
36
+ self._lock = threading.RLock()
37
+ self._closed = False
38
+
39
+ def register_resource(self, resource: ResourceProtocol) -> None:
40
+ """Register a resource for automatic cleanup."""
41
+ with self._lock:
42
+ if self._closed:
43
+ # If already closed, immediately clean up the resource
44
+ asyncio.create_task(resource.cleanup())
45
+ return
46
+ self._resources.append(resource)
47
+
48
+ def register_cleanup_callback(
49
+ self, callback: t.Callable[[], t.Awaitable[None]]
50
+ ) -> None:
51
+ """Register a cleanup callback to be called during shutdown."""
52
+ with self._lock:
53
+ if self._closed:
54
+ # If already closed, immediately call the callback
55
+ coro = callback()
56
+ if asyncio.iscoroutine(coro):
57
+ asyncio.ensure_future(coro)
58
+ return
59
+ self._cleanup_callbacks.append(callback)
60
+
61
+ async def cleanup_all(self) -> None:
62
+ """Clean up all registered resources."""
63
+ with self._lock:
64
+ if self._closed:
65
+ return
66
+ self._closed = True
67
+
68
+ resources = self._resources.copy()
69
+ callbacks = self._cleanup_callbacks.copy()
70
+
71
+ # Clean up resources
72
+ for resource in resources:
73
+ try:
74
+ await resource.cleanup()
75
+ except Exception as e:
76
+ self.logger.warning(f"Error cleaning up resource {resource}: {e}")
77
+
78
+ # Call cleanup callbacks
79
+ for callback in callbacks:
80
+ try:
81
+ await callback()
82
+ except Exception as e:
83
+ self.logger.warning(f"Error in cleanup callback: {e}")
84
+
85
+ with self._lock:
86
+ self._resources.clear()
87
+ self._cleanup_callbacks.clear()
88
+
89
+ async def __aenter__(self) -> "ResourceManager":
90
+ return self
91
+
92
+ async def __aexit__(
93
+ self,
94
+ exc_type: type[BaseException] | None,
95
+ exc_val: BaseException | None,
96
+ exc_tb: TracebackType | None,
97
+ ) -> None:
98
+ await self.cleanup_all()
99
+
100
+
101
+ class ManagedResource(ABC):
102
+ """Base class for managed resources with automatic cleanup."""
103
+
104
+ def __init__(self, manager: ResourceManager | None = None) -> None:
105
+ self.manager = manager
106
+ self._closed = False
107
+
108
+ if manager:
109
+ manager.register_resource(self)
110
+
111
+ @abstractmethod
112
+ async def cleanup(self) -> None:
113
+ """Clean up the resource. Must be implemented by subclasses."""
114
+ pass
115
+
116
+ async def close(self) -> None:
117
+ """Manually close the resource."""
118
+ if not self._closed:
119
+ self._closed = True
120
+ await self.cleanup()
121
+
122
+ def __del__(self) -> None:
123
+ """Ensure cleanup is called even if explicitly forgotten."""
124
+ if not self._closed:
125
+ # Create cleanup task if event loop is available
126
+ with contextlib.suppress(RuntimeError):
127
+ loop = asyncio.get_event_loop()
128
+ if loop.is_running():
129
+ asyncio.create_task(self.cleanup())
130
+
131
+
132
+ class ManagedTemporaryFile(ManagedResource):
133
+ """Temporary file with automatic cleanup on error scenarios."""
134
+
135
+ def __init__(
136
+ self,
137
+ suffix: str = "",
138
+ prefix: str = "crackerjack-",
139
+ manager: ResourceManager | None = None,
140
+ ) -> None:
141
+ super().__init__(manager)
142
+ self.temp_file = tempfile.NamedTemporaryFile(
143
+ suffix=suffix,
144
+ prefix=prefix,
145
+ delete=False, # We'll handle deletion ourselves
146
+ )
147
+ self.path = Path(self.temp_file.name)
148
+
149
+ async def cleanup(self) -> None:
150
+ """Clean up temporary file."""
151
+ if not self._closed:
152
+ self._closed = True
153
+
154
+ # Close file handle
155
+ if not self.temp_file.closed:
156
+ self.temp_file.close()
157
+
158
+ # Remove file if it exists
159
+ try:
160
+ if self.path.exists():
161
+ self.path.unlink()
162
+ except OSError as e:
163
+ # Log but don't raise - cleanup should be best effort
164
+ logging.getLogger(__name__).warning(
165
+ f"Failed to remove temporary file {self.path}: {e}"
166
+ )
167
+
168
+ def write_text(self, content: str, encoding: str = "utf-8") -> None:
169
+ """Write text content to the temporary file."""
170
+ if self._closed:
171
+ raise RuntimeError("Cannot write to closed temporary file")
172
+ self.path.write_text(content, encoding=encoding)
173
+
174
+ def read_text(self, encoding: str = "utf-8") -> str:
175
+ """Read text content from the temporary file."""
176
+ return self.path.read_text(encoding=encoding)
177
+
178
+
179
+ class ManagedTemporaryDirectory(ManagedResource):
180
+ """Temporary directory with automatic cleanup on error scenarios."""
181
+
182
+ def __init__(
183
+ self,
184
+ suffix: str = "",
185
+ prefix: str = "crackerjack-",
186
+ manager: ResourceManager | None = None,
187
+ ) -> None:
188
+ super().__init__(manager)
189
+ self.temp_dir = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
190
+ self.path = Path(self.temp_dir)
191
+
192
+ async def cleanup(self) -> None:
193
+ """Clean up temporary directory and all contents."""
194
+ if not self._closed:
195
+ self._closed = True
196
+
197
+ try:
198
+ import shutil
199
+
200
+ if self.path.exists():
201
+ shutil.rmtree(self.path)
202
+ except OSError as e:
203
+ logging.getLogger(__name__).warning(
204
+ f"Failed to remove temporary directory {self.path}: {e}"
205
+ )
206
+
207
+
208
+ class ManagedProcess(ManagedResource):
209
+ """Process with automatic cleanup on error scenarios."""
210
+
211
+ def __init__(
212
+ self,
213
+ process: asyncio.subprocess.Process,
214
+ timeout: float = 30.0,
215
+ manager: ResourceManager | None = None,
216
+ ) -> None:
217
+ super().__init__(manager)
218
+ self.process = process
219
+ self.timeout = timeout
220
+
221
+ async def cleanup(self) -> None:
222
+ """Clean up process with graceful termination."""
223
+ if not self._closed and self.process.returncode is None:
224
+ self._closed = True
225
+
226
+ try:
227
+ # Try graceful termination first
228
+ self.process.terminate()
229
+
230
+ try:
231
+ await asyncio.wait_for(self.process.wait(), timeout=5.0)
232
+ except TimeoutError:
233
+ # Force kill if graceful termination fails
234
+ self.process.kill()
235
+ try:
236
+ await asyncio.wait_for(self.process.wait(), timeout=2.0)
237
+ except TimeoutError:
238
+ logging.getLogger(__name__).warning(
239
+ f"Process {self.process.pid} did not terminate after force kill"
240
+ )
241
+
242
+ except ProcessLookupError:
243
+ # Process already terminated
244
+ pass
245
+ except Exception as e:
246
+ logging.getLogger(__name__).warning(
247
+ f"Error cleaning up process {self.process.pid}: {e}"
248
+ )
249
+
250
+
251
+ class ManagedTask(ManagedResource):
252
+ """Asyncio task with automatic cancellation on error scenarios."""
253
+
254
+ def __init__(
255
+ self,
256
+ task: asyncio.Task[t.Any],
257
+ timeout: float = 30.0,
258
+ manager: ResourceManager | None = None,
259
+ ) -> None:
260
+ super().__init__(manager)
261
+ self.task = task
262
+ self.timeout = timeout
263
+
264
+ async def cleanup(self) -> None:
265
+ """Clean up task with cancellation."""
266
+ if not self._closed and not self.task.done():
267
+ self._closed = True
268
+
269
+ self.task.cancel()
270
+
271
+ try:
272
+ await asyncio.wait_for(self.task, timeout=self.timeout)
273
+ except (TimeoutError, asyncio.CancelledError):
274
+ # Expected when cancelling or timing out
275
+ pass
276
+ except Exception as e:
277
+ logging.getLogger(__name__).warning(f"Error cleaning up task: {e}")
278
+
279
+
280
+ class ManagedFileHandle(ManagedResource):
281
+ """File handle with automatic closing on error scenarios."""
282
+
283
+ def __init__(
284
+ self,
285
+ file_handle: t.IO[t.Any],
286
+ manager: ResourceManager | None = None,
287
+ ) -> None:
288
+ super().__init__(manager)
289
+ self.file_handle = file_handle
290
+
291
+ async def cleanup(self) -> None:
292
+ """Clean up file handle."""
293
+ if not self._closed and not self.file_handle.closed:
294
+ self._closed = True
295
+
296
+ try:
297
+ self.file_handle.close()
298
+ except Exception as e:
299
+ logging.getLogger(__name__).warning(f"Error closing file handle: {e}")
300
+
301
+
302
+ class ResourceContext:
303
+ """Context manager for automatic resource management."""
304
+
305
+ def __init__(self) -> None:
306
+ self.resource_manager = ResourceManager()
307
+
308
+ def managed_temp_file(
309
+ self,
310
+ suffix: str = "",
311
+ prefix: str = "crackerjack-",
312
+ ) -> ManagedTemporaryFile:
313
+ """Create a managed temporary file."""
314
+ return ManagedTemporaryFile(suffix, prefix, self.resource_manager)
315
+
316
+ def managed_temp_dir(
317
+ self,
318
+ suffix: str = "",
319
+ prefix: str = "crackerjack-",
320
+ ) -> ManagedTemporaryDirectory:
321
+ """Create a managed temporary directory."""
322
+ return ManagedTemporaryDirectory(suffix, prefix, self.resource_manager)
323
+
324
+ def managed_process(
325
+ self,
326
+ process: asyncio.subprocess.Process,
327
+ timeout: float = 30.0,
328
+ ) -> ManagedProcess:
329
+ """Create a managed process."""
330
+ return ManagedProcess(process, timeout, self.resource_manager)
331
+
332
+ def managed_task(
333
+ self,
334
+ task: asyncio.Task[t.Any],
335
+ timeout: float = 30.0,
336
+ ) -> ManagedTask:
337
+ """Create a managed task."""
338
+ return ManagedTask(task, timeout, self.resource_manager)
339
+
340
+ def managed_file(
341
+ self,
342
+ file_handle: t.IO[t.Any],
343
+ ) -> ManagedFileHandle:
344
+ """Create a managed file handle."""
345
+ return ManagedFileHandle(file_handle, self.resource_manager)
346
+
347
+ async def __aenter__(self) -> "ResourceContext":
348
+ await self.resource_manager.__aenter__()
349
+ return self
350
+
351
+ async def __aexit__(
352
+ self,
353
+ exc_type: type[BaseException] | None,
354
+ exc_val: BaseException | None,
355
+ exc_tb: TracebackType | None,
356
+ ) -> None:
357
+ await self.resource_manager.__aexit__(exc_type, exc_val, exc_tb)
358
+
359
+
360
+ # Global resource manager registry
361
+ _global_managers: weakref.WeakSet[ResourceManager] = weakref.WeakSet()
362
+
363
+
364
+ def register_global_resource_manager(manager: ResourceManager) -> None:
365
+ """Register a resource manager for global cleanup."""
366
+ _global_managers.add(manager)
367
+
368
+
369
+ async def cleanup_all_global_resources() -> None:
370
+ """Clean up all globally registered resource managers."""
371
+ managers = list(_global_managers)
372
+
373
+ cleanup_tasks = [asyncio.create_task(manager.cleanup_all()) for manager in managers]
374
+
375
+ if cleanup_tasks:
376
+ await asyncio.gather(*cleanup_tasks, return_exceptions=True)
377
+
378
+
379
+ # Cleanup helper context managers
380
+ @contextlib.asynccontextmanager
381
+ async def with_resource_cleanup():
382
+ """Context manager that ensures resource cleanup on any exit."""
383
+ async with ResourceContext() as ctx:
384
+ yield ctx
385
+
386
+
387
+ @contextlib.asynccontextmanager
388
+ async def with_temp_file(suffix: str = "", prefix: str = "crackerjack-"):
389
+ """Context manager for temporary file with automatic cleanup."""
390
+ async with ResourceContext() as ctx:
391
+ temp_file = ctx.managed_temp_file(suffix, prefix)
392
+ try:
393
+ yield temp_file
394
+ finally:
395
+ await temp_file.cleanup()
396
+
397
+
398
+ @contextlib.asynccontextmanager
399
+ async def with_temp_dir(suffix: str = "", prefix: str = "crackerjack-"):
400
+ """Context manager for temporary directory with automatic cleanup."""
401
+ async with ResourceContext() as ctx:
402
+ temp_dir = ctx.managed_temp_dir(suffix, prefix)
403
+ try:
404
+ yield temp_dir
405
+ finally:
406
+ await temp_dir.cleanup()
407
+
408
+
409
+ @contextlib.asynccontextmanager
410
+ async def with_managed_process(
411
+ process: asyncio.subprocess.Process,
412
+ timeout: float = 30.0,
413
+ ):
414
+ """Context manager for process with automatic cleanup."""
415
+ async with ResourceContext() as ctx:
416
+ managed_proc = ctx.managed_process(process, timeout)
417
+ try:
418
+ yield managed_proc
419
+ finally:
420
+ await managed_proc.cleanup()
421
+
422
+
423
+ # Enhanced error handling utilities
424
+ class ResourceLeakDetector:
425
+ """Detector for potential resource leaks during development."""
426
+
427
+ def __init__(self) -> None:
428
+ self.open_files: set[str] = set()
429
+ self.active_processes: set[int] = set()
430
+ self.active_tasks: set[asyncio.Task[t.Any]] = set()
431
+ self._start_time = time.time()
432
+
433
+ def track_file(self, file_path: str) -> None:
434
+ """Track an opened file."""
435
+ self.open_files.add(file_path)
436
+
437
+ def untrack_file(self, file_path: str) -> None:
438
+ """Untrack a closed file."""
439
+ self.open_files.discard(file_path)
440
+
441
+ def track_process(self, pid: int) -> None:
442
+ """Track a spawned process."""
443
+ self.active_processes.add(pid)
444
+
445
+ def untrack_process(self, pid: int) -> None:
446
+ """Untrack a terminated process."""
447
+ self.active_processes.discard(pid)
448
+
449
+ def track_task(self, task: asyncio.Task[t.Any]) -> None:
450
+ """Track an active task."""
451
+ self.active_tasks.add(task)
452
+
453
+ def untrack_task(self, task: asyncio.Task[t.Any]) -> None:
454
+ """Untrack a completed task."""
455
+ self.active_tasks.discard(task)
456
+
457
+ def get_leak_report(self) -> dict[str, t.Any]:
458
+ """Get a report of potential resource leaks."""
459
+ return {
460
+ "duration_seconds": time.time() - self._start_time,
461
+ "open_files": list(self.open_files),
462
+ "active_processes": list(self.active_processes),
463
+ "active_tasks": len([t for t in self.active_tasks if not t.done()]),
464
+ "total_tracked_files": len(self.open_files),
465
+ "total_tracked_processes": len(self.active_processes),
466
+ "total_tracked_tasks": len(self.active_tasks),
467
+ }
468
+
469
+ def has_potential_leaks(self) -> bool:
470
+ """Check if there are potential resource leaks."""
471
+ return bool(
472
+ self.open_files
473
+ or self.active_processes
474
+ or any(not t.done() for t in self.active_tasks)
475
+ )
476
+
477
+
478
+ # Development-time resource leak detection
479
+ _leak_detector: ResourceLeakDetector | None = None
480
+
481
+
482
+ def enable_leak_detection() -> ResourceLeakDetector:
483
+ """Enable resource leak detection for development."""
484
+ global _leak_detector
485
+ _leak_detector = ResourceLeakDetector()
486
+ return _leak_detector
487
+
488
+
489
+ def get_leak_detector() -> ResourceLeakDetector | None:
490
+ """Get the current resource leak detector, if enabled."""
491
+ return _leak_detector
492
+
493
+
494
+ def disable_leak_detection() -> dict[str, t.Any] | None:
495
+ """Disable resource leak detection and return final report."""
496
+ global _leak_detector
497
+ if _leak_detector:
498
+ report = _leak_detector.get_leak_report()
499
+ _leak_detector = None
500
+ return report
501
+ return None