crackerjack 0.32.0__py3-none-any.whl → 0.33.1__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 (200) hide show
  1. crackerjack/__main__.py +1350 -34
  2. crackerjack/adapters/__init__.py +17 -0
  3. crackerjack/adapters/lsp_client.py +358 -0
  4. crackerjack/adapters/rust_tool_adapter.py +194 -0
  5. crackerjack/adapters/rust_tool_manager.py +193 -0
  6. crackerjack/adapters/skylos_adapter.py +231 -0
  7. crackerjack/adapters/zuban_adapter.py +560 -0
  8. crackerjack/agents/base.py +7 -3
  9. crackerjack/agents/coordinator.py +271 -33
  10. crackerjack/agents/documentation_agent.py +9 -15
  11. crackerjack/agents/dry_agent.py +3 -15
  12. crackerjack/agents/formatting_agent.py +1 -1
  13. crackerjack/agents/import_optimization_agent.py +36 -180
  14. crackerjack/agents/performance_agent.py +17 -98
  15. crackerjack/agents/performance_helpers.py +7 -31
  16. crackerjack/agents/proactive_agent.py +1 -3
  17. crackerjack/agents/refactoring_agent.py +16 -85
  18. crackerjack/agents/refactoring_helpers.py +7 -42
  19. crackerjack/agents/security_agent.py +9 -48
  20. crackerjack/agents/test_creation_agent.py +356 -513
  21. crackerjack/agents/test_specialist_agent.py +0 -4
  22. crackerjack/api.py +6 -25
  23. crackerjack/cli/cache_handlers.py +204 -0
  24. crackerjack/cli/cache_handlers_enhanced.py +683 -0
  25. crackerjack/cli/facade.py +100 -0
  26. crackerjack/cli/handlers.py +224 -9
  27. crackerjack/cli/interactive.py +6 -4
  28. crackerjack/cli/options.py +642 -55
  29. crackerjack/cli/utils.py +2 -1
  30. crackerjack/code_cleaner.py +58 -117
  31. crackerjack/config/global_lock_config.py +8 -48
  32. crackerjack/config/hooks.py +53 -62
  33. crackerjack/core/async_workflow_orchestrator.py +24 -34
  34. crackerjack/core/autofix_coordinator.py +3 -17
  35. crackerjack/core/enhanced_container.py +64 -6
  36. crackerjack/core/file_lifecycle.py +12 -89
  37. crackerjack/core/performance.py +2 -2
  38. crackerjack/core/performance_monitor.py +15 -55
  39. crackerjack/core/phase_coordinator.py +257 -218
  40. crackerjack/core/resource_manager.py +14 -90
  41. crackerjack/core/service_watchdog.py +62 -95
  42. crackerjack/core/session_coordinator.py +149 -0
  43. crackerjack/core/timeout_manager.py +14 -72
  44. crackerjack/core/websocket_lifecycle.py +13 -78
  45. crackerjack/core/workflow_orchestrator.py +558 -240
  46. crackerjack/docs/INDEX.md +11 -0
  47. crackerjack/docs/generated/api/API_REFERENCE.md +10895 -0
  48. crackerjack/docs/generated/api/CLI_REFERENCE.md +109 -0
  49. crackerjack/docs/generated/api/CROSS_REFERENCES.md +1755 -0
  50. crackerjack/docs/generated/api/PROTOCOLS.md +3 -0
  51. crackerjack/docs/generated/api/SERVICES.md +1252 -0
  52. crackerjack/documentation/__init__.py +31 -0
  53. crackerjack/documentation/ai_templates.py +756 -0
  54. crackerjack/documentation/dual_output_generator.py +765 -0
  55. crackerjack/documentation/mkdocs_integration.py +518 -0
  56. crackerjack/documentation/reference_generator.py +977 -0
  57. crackerjack/dynamic_config.py +55 -50
  58. crackerjack/executors/async_hook_executor.py +10 -15
  59. crackerjack/executors/cached_hook_executor.py +117 -43
  60. crackerjack/executors/hook_executor.py +8 -34
  61. crackerjack/executors/hook_lock_manager.py +26 -183
  62. crackerjack/executors/individual_hook_executor.py +13 -11
  63. crackerjack/executors/lsp_aware_hook_executor.py +270 -0
  64. crackerjack/executors/tool_proxy.py +417 -0
  65. crackerjack/hooks/lsp_hook.py +79 -0
  66. crackerjack/intelligence/adaptive_learning.py +25 -10
  67. crackerjack/intelligence/agent_orchestrator.py +2 -5
  68. crackerjack/intelligence/agent_registry.py +34 -24
  69. crackerjack/intelligence/agent_selector.py +5 -7
  70. crackerjack/interactive.py +17 -6
  71. crackerjack/managers/async_hook_manager.py +0 -1
  72. crackerjack/managers/hook_manager.py +79 -1
  73. crackerjack/managers/publish_manager.py +66 -13
  74. crackerjack/managers/test_command_builder.py +5 -17
  75. crackerjack/managers/test_executor.py +1 -3
  76. crackerjack/managers/test_manager.py +109 -7
  77. crackerjack/managers/test_manager_backup.py +10 -9
  78. crackerjack/mcp/cache.py +2 -2
  79. crackerjack/mcp/client_runner.py +1 -1
  80. crackerjack/mcp/context.py +191 -68
  81. crackerjack/mcp/dashboard.py +7 -5
  82. crackerjack/mcp/enhanced_progress_monitor.py +31 -28
  83. crackerjack/mcp/file_monitor.py +30 -23
  84. crackerjack/mcp/progress_components.py +31 -21
  85. crackerjack/mcp/progress_monitor.py +50 -53
  86. crackerjack/mcp/rate_limiter.py +6 -6
  87. crackerjack/mcp/server_core.py +161 -32
  88. crackerjack/mcp/service_watchdog.py +2 -1
  89. crackerjack/mcp/state.py +4 -7
  90. crackerjack/mcp/task_manager.py +11 -9
  91. crackerjack/mcp/tools/core_tools.py +174 -33
  92. crackerjack/mcp/tools/error_analyzer.py +3 -2
  93. crackerjack/mcp/tools/execution_tools.py +15 -12
  94. crackerjack/mcp/tools/execution_tools_backup.py +42 -30
  95. crackerjack/mcp/tools/intelligence_tool_registry.py +7 -5
  96. crackerjack/mcp/tools/intelligence_tools.py +5 -2
  97. crackerjack/mcp/tools/monitoring_tools.py +33 -70
  98. crackerjack/mcp/tools/proactive_tools.py +24 -11
  99. crackerjack/mcp/tools/progress_tools.py +5 -8
  100. crackerjack/mcp/tools/utility_tools.py +20 -14
  101. crackerjack/mcp/tools/workflow_executor.py +62 -40
  102. crackerjack/mcp/websocket/app.py +8 -0
  103. crackerjack/mcp/websocket/endpoints.py +352 -357
  104. crackerjack/mcp/websocket/jobs.py +40 -57
  105. crackerjack/mcp/websocket/monitoring_endpoints.py +2935 -0
  106. crackerjack/mcp/websocket/server.py +7 -25
  107. crackerjack/mcp/websocket/websocket_handler.py +6 -17
  108. crackerjack/mixins/__init__.py +3 -0
  109. crackerjack/mixins/error_handling.py +145 -0
  110. crackerjack/models/config.py +21 -1
  111. crackerjack/models/config_adapter.py +49 -1
  112. crackerjack/models/protocols.py +176 -107
  113. crackerjack/models/resource_protocols.py +55 -210
  114. crackerjack/models/task.py +3 -0
  115. crackerjack/monitoring/ai_agent_watchdog.py +13 -13
  116. crackerjack/monitoring/metrics_collector.py +426 -0
  117. crackerjack/monitoring/regression_prevention.py +8 -8
  118. crackerjack/monitoring/websocket_server.py +643 -0
  119. crackerjack/orchestration/advanced_orchestrator.py +11 -6
  120. crackerjack/orchestration/coverage_improvement.py +3 -3
  121. crackerjack/orchestration/execution_strategies.py +26 -6
  122. crackerjack/orchestration/test_progress_streamer.py +8 -5
  123. crackerjack/plugins/base.py +2 -2
  124. crackerjack/plugins/hooks.py +7 -0
  125. crackerjack/plugins/managers.py +11 -8
  126. crackerjack/security/__init__.py +0 -1
  127. crackerjack/security/audit.py +90 -105
  128. crackerjack/services/anomaly_detector.py +392 -0
  129. crackerjack/services/api_extractor.py +615 -0
  130. crackerjack/services/backup_service.py +2 -2
  131. crackerjack/services/bounded_status_operations.py +15 -152
  132. crackerjack/services/cache.py +127 -1
  133. crackerjack/services/changelog_automation.py +395 -0
  134. crackerjack/services/config.py +18 -11
  135. crackerjack/services/config_merge.py +30 -85
  136. crackerjack/services/config_template.py +506 -0
  137. crackerjack/services/contextual_ai_assistant.py +48 -22
  138. crackerjack/services/coverage_badge_service.py +171 -0
  139. crackerjack/services/coverage_ratchet.py +41 -17
  140. crackerjack/services/debug.py +3 -3
  141. crackerjack/services/dependency_analyzer.py +460 -0
  142. crackerjack/services/dependency_monitor.py +14 -11
  143. crackerjack/services/documentation_generator.py +491 -0
  144. crackerjack/services/documentation_service.py +675 -0
  145. crackerjack/services/enhanced_filesystem.py +6 -5
  146. crackerjack/services/enterprise_optimizer.py +865 -0
  147. crackerjack/services/error_pattern_analyzer.py +676 -0
  148. crackerjack/services/file_hasher.py +1 -1
  149. crackerjack/services/git.py +41 -45
  150. crackerjack/services/health_metrics.py +10 -8
  151. crackerjack/services/heatmap_generator.py +735 -0
  152. crackerjack/services/initialization.py +30 -33
  153. crackerjack/services/input_validator.py +5 -97
  154. crackerjack/services/intelligent_commit.py +327 -0
  155. crackerjack/services/log_manager.py +15 -12
  156. crackerjack/services/logging.py +4 -3
  157. crackerjack/services/lsp_client.py +628 -0
  158. crackerjack/services/memory_optimizer.py +409 -0
  159. crackerjack/services/metrics.py +42 -33
  160. crackerjack/services/parallel_executor.py +416 -0
  161. crackerjack/services/pattern_cache.py +1 -1
  162. crackerjack/services/pattern_detector.py +6 -6
  163. crackerjack/services/performance_benchmarks.py +250 -576
  164. crackerjack/services/performance_cache.py +382 -0
  165. crackerjack/services/performance_monitor.py +565 -0
  166. crackerjack/services/predictive_analytics.py +510 -0
  167. crackerjack/services/quality_baseline.py +234 -0
  168. crackerjack/services/quality_baseline_enhanced.py +646 -0
  169. crackerjack/services/quality_intelligence.py +785 -0
  170. crackerjack/services/regex_patterns.py +605 -524
  171. crackerjack/services/regex_utils.py +43 -123
  172. crackerjack/services/secure_path_utils.py +5 -164
  173. crackerjack/services/secure_status_formatter.py +30 -141
  174. crackerjack/services/secure_subprocess.py +11 -92
  175. crackerjack/services/security.py +61 -30
  176. crackerjack/services/security_logger.py +18 -22
  177. crackerjack/services/server_manager.py +124 -16
  178. crackerjack/services/status_authentication.py +16 -159
  179. crackerjack/services/status_security_manager.py +4 -131
  180. crackerjack/services/terminal_utils.py +0 -0
  181. crackerjack/services/thread_safe_status_collector.py +19 -125
  182. crackerjack/services/unified_config.py +21 -13
  183. crackerjack/services/validation_rate_limiter.py +5 -54
  184. crackerjack/services/version_analyzer.py +459 -0
  185. crackerjack/services/version_checker.py +1 -1
  186. crackerjack/services/websocket_resource_limiter.py +10 -144
  187. crackerjack/services/zuban_lsp_service.py +390 -0
  188. crackerjack/slash_commands/__init__.py +2 -7
  189. crackerjack/slash_commands/run.md +2 -2
  190. crackerjack/tools/validate_input_validator_patterns.py +14 -40
  191. crackerjack/tools/validate_regex_patterns.py +19 -48
  192. {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.dist-info}/METADATA +197 -26
  193. crackerjack-0.33.1.dist-info/RECORD +229 -0
  194. crackerjack/CLAUDE.md +0 -207
  195. crackerjack/RULES.md +0 -380
  196. crackerjack/py313.py +0 -234
  197. crackerjack-0.32.0.dist-info/RECORD +0 -180
  198. {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.dist-info}/WHEEL +0 -0
  199. {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.dist-info}/entry_points.txt +0 -0
  200. {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.dist-info}/licenses/LICENSE +0 -0
@@ -11,11 +11,16 @@ from rich.console import Console
11
11
 
12
12
  from crackerjack.models.protocols import (
13
13
  ConfigMergeServiceProtocol,
14
+ ConfigurationServiceProtocol,
15
+ CoverageRatchetProtocol,
14
16
  FileSystemInterface,
15
17
  GitInterface,
16
18
  HookManager,
19
+ InitializationServiceProtocol,
17
20
  PublishManager,
21
+ SecurityServiceProtocol,
18
22
  TestManagerProtocol,
23
+ UnifiedConfigurationServiceProtocol,
19
24
  )
20
25
  from crackerjack.services.logging import get_logger
21
26
 
@@ -39,7 +44,7 @@ class ServiceDescriptor:
39
44
  created_count: int = 0
40
45
  dependencies: list[type] = field(default_factory=list)
41
46
 
42
- def __post_init__(self):
47
+ def __post_init__(self) -> None:
43
48
  if self.implementation is self.factory is self.instance is None:
44
49
  msg = "Must provide either implementation, factory, or instance"
45
50
  raise ValueError(msg)
@@ -110,7 +115,6 @@ class DependencyResolver:
110
115
  dependency = self.container.get(param.annotation)
111
116
  kwargs[param_name] = dependency
112
117
  except Exception as e:
113
- # Only log as warning for required parameters, debug for optional ones with defaults
114
118
  if param.default == inspect.Parameter.empty:
115
119
  self.logger.warning(
116
120
  "Could not inject dependency",
@@ -142,7 +146,7 @@ class DependencyResolver:
142
146
 
143
147
  def _build_constructor_kwargs(self, implementation: type) -> dict[str, Any]:
144
148
  init_sig = inspect.signature(implementation.__init__)
145
- kwargs = {}
149
+ kwargs: dict[str, Any] = {}
146
150
 
147
151
  for param_name, param in init_sig.parameters.items():
148
152
  if param_name == "self":
@@ -296,7 +300,7 @@ class EnhancedDependencyContainer:
296
300
  self._current_scope = scope
297
301
 
298
302
  def get_service_info(self) -> dict[str, Any]:
299
- info = {}
303
+ info: dict[str, Any] = {}
300
304
 
301
305
  with self._lock:
302
306
  for key, descriptor in self._services.items():
@@ -387,7 +391,7 @@ class EnhancedDependencyContainer:
387
391
  def _get_service_key(self, interface: type) -> str:
388
392
  return f"{interface.__module__}.{interface.__name__}"
389
393
 
390
- def __enter__(self):
394
+ def __enter__(self) -> "EnhancedDependencyContainer":
391
395
  return self
392
396
 
393
397
  def __exit__(
@@ -470,6 +474,56 @@ class ServiceCollectionBuilder:
470
474
 
471
475
  return self
472
476
 
477
+ def add_service_protocols(self) -> "ServiceCollectionBuilder":
478
+ console = self.console or Console(force_terminal=True)
479
+ pkg_path = self.pkg_path or Path.cwd()
480
+
481
+ def create_coverage_ratchet() -> CoverageRatchetProtocol:
482
+ from crackerjack.services.coverage_ratchet import CoverageRatchetService
483
+
484
+ return CoverageRatchetService(pkg_path, console)
485
+
486
+ self.container.register_transient(
487
+ CoverageRatchetProtocol,
488
+ factory=create_coverage_ratchet,
489
+ )
490
+
491
+ def create_configuration_service() -> ConfigurationServiceProtocol:
492
+ from crackerjack.services.config import ConfigurationService
493
+
494
+ return ConfigurationService(console=console, pkg_path=pkg_path)
495
+
496
+ self.container.register_transient(
497
+ ConfigurationServiceProtocol,
498
+ factory=create_configuration_service,
499
+ )
500
+
501
+ def create_security_service() -> SecurityServiceProtocol:
502
+ from crackerjack.services.security import SecurityService
503
+
504
+ return SecurityService()
505
+
506
+ self.container.register_transient(
507
+ SecurityServiceProtocol,
508
+ factory=create_security_service,
509
+ )
510
+
511
+ def create_initialization_service() -> InitializationServiceProtocol:
512
+ from crackerjack.services.filesystem import FileSystemService
513
+ from crackerjack.services.git import GitService
514
+ from crackerjack.services.initialization import InitializationService
515
+
516
+ filesystem = FileSystemService()
517
+ git_service = GitService(console, pkg_path)
518
+ return InitializationService(console, filesystem, git_service, pkg_path)
519
+
520
+ self.container.register_transient(
521
+ InitializationServiceProtocol,
522
+ factory=create_initialization_service,
523
+ )
524
+
525
+ return self
526
+
473
527
  def add_configuration_services(self) -> "ServiceCollectionBuilder":
474
528
  console = self.console or Console(force_terminal=True)
475
529
  pkg_path = self.pkg_path or Path.cwd()
@@ -481,7 +535,11 @@ class ServiceCollectionBuilder:
481
535
  factory=lambda: UnifiedConfigurationService(console, pkg_path),
482
536
  )
483
537
 
484
- # Register ConfigMergeService for smart configuration merging
538
+ self.container.register_singleton(
539
+ UnifiedConfigurationServiceProtocol,
540
+ factory=lambda: self.container.get(UnifiedConfigurationService),
541
+ )
542
+
485
543
  from crackerjack.services.config_merge import ConfigMergeService
486
544
 
487
545
  def create_config_merge_service() -> ConfigMergeService:
@@ -1,9 +1,3 @@
1
- """File operations with RAII patterns and comprehensive error handling.
2
-
3
- Provides robust file handling with automatic cleanup, atomic operations,
4
- and comprehensive error recovery patterns.
5
- """
6
-
7
1
  import asyncio
8
2
  import contextlib
9
3
  import fcntl
@@ -19,8 +13,6 @@ from .resource_manager import ResourceManager
19
13
 
20
14
 
21
15
  class AtomicFileWriter(AbstractFileResource):
22
- """Atomic file writer with automatic cleanup and rollback on errors."""
23
-
24
16
  def __init__(
25
17
  self,
26
18
  target_path: Path,
@@ -39,32 +31,24 @@ class AtomicFileWriter(AbstractFileResource):
39
31
  manager.register_resource(self)
40
32
 
41
33
  async def _do_initialize(self) -> None:
42
- """Initialize atomic file writer."""
43
- # Create temporary file in same directory as target
44
34
  self.temp_path = self.path.parent / f".{self.path.name}.tmp.{os.getpid()}"
45
35
 
46
- # Create backup if requested and target exists
47
36
  if self.backup and self.path.exists():
48
37
  self.backup_path = self.path.with_suffix(f"{self.path.suffix}.bak")
49
38
  shutil.copy2(self.path, self.backup_path)
50
39
 
51
- # Open temporary file for writing
52
40
  self._file_handle = self.temp_path.open("w", encoding="utf-8")
53
41
 
54
42
  async def _do_cleanup(self) -> None:
55
- """Clean up temporary files and handles."""
56
- # Close file handle
57
43
  if self._file_handle and not self._file_handle.closed:
58
44
  self._file_handle.close()
59
45
 
60
- # Remove temporary file if it exists
61
46
  if self.temp_path and self.temp_path.exists():
62
47
  try:
63
48
  self.temp_path.unlink()
64
49
  except OSError as e:
65
50
  self.logger.warning(f"Failed to remove temp file {self.temp_path}: {e}")
66
51
 
67
- # Remove backup file if cleanup is successful
68
52
  if self.backup_path and self.backup_path.exists():
69
53
  try:
70
54
  self.backup_path.unlink()
@@ -74,43 +58,35 @@ class AtomicFileWriter(AbstractFileResource):
74
58
  )
75
59
 
76
60
  def write(self, content: str) -> None:
77
- """Write content to the temporary file."""
78
61
  if not self._file_handle:
79
62
  raise RuntimeError("AtomicFileWriter not initialized")
80
63
  self._file_handle.write(content)
81
64
 
82
65
  def writelines(self, lines: t.Iterable[str]) -> None:
83
- """Write multiple lines to the temporary file."""
84
66
  if not self._file_handle:
85
67
  raise RuntimeError("AtomicFileWriter not initialized")
86
68
  self._file_handle.writelines(lines)
87
69
 
88
70
  def flush(self) -> None:
89
- """Flush the temporary file."""
90
71
  if not self._file_handle:
91
72
  raise RuntimeError("AtomicFileWriter not initialized")
92
73
  self._file_handle.flush()
93
74
  os.fsync(self._file_handle.fileno())
94
75
 
95
76
  async def commit(self) -> None:
96
- """Atomically commit the changes to the target file."""
97
77
  if not self.temp_path:
98
78
  raise RuntimeError("AtomicFileWriter not initialized")
99
79
 
100
- # Ensure all data is written
101
80
  self.flush()
102
81
 
103
- # Close temporary file
104
82
  if self._file_handle:
105
83
  self._file_handle.close()
106
84
  self._file_handle = None
107
85
 
108
- # Atomic move
109
86
  try:
110
87
  self.temp_path.replace(self.path)
111
88
  self.logger.debug(f"Successfully committed changes to {self.path}")
112
89
  except OSError as e:
113
- # Restore from backup if available
114
90
  if self.backup_path and self.backup_path.exists():
115
91
  try:
116
92
  self.backup_path.replace(self.path)
@@ -122,7 +98,6 @@ class AtomicFileWriter(AbstractFileResource):
122
98
  raise RuntimeError(f"Failed to commit changes to {self.path}") from e
123
99
 
124
100
  async def rollback(self) -> None:
125
- """Rollback changes and restore from backup if available."""
126
101
  if self.backup_path and self.backup_path.exists():
127
102
  try:
128
103
  self.backup_path.replace(self.path)
@@ -133,8 +108,6 @@ class AtomicFileWriter(AbstractFileResource):
133
108
 
134
109
 
135
110
  class LockedFileResource(AbstractFileResource):
136
- """File resource with exclusive locking for concurrent access protection."""
137
-
138
111
  def __init__(
139
112
  self,
140
113
  path: Path,
@@ -152,14 +125,10 @@ class LockedFileResource(AbstractFileResource):
152
125
  manager.register_resource(self)
153
126
 
154
127
  async def _do_initialize(self) -> None:
155
- """Initialize locked file resource."""
156
- # Ensure parent directory exists
157
128
  self.path.parent.mkdir(parents=True, exist_ok=True)
158
129
 
159
- # Open file
160
130
  self._file_handle = self.path.open(self.mode)
161
131
 
162
- # Acquire exclusive lock with timeout
163
132
  start_time = time.time()
164
133
  while time.time() - start_time < self.timeout:
165
134
  try:
@@ -174,10 +143,8 @@ class LockedFileResource(AbstractFileResource):
174
143
  )
175
144
 
176
145
  async def _do_cleanup(self) -> None:
177
- """Clean up locked file resource."""
178
146
  if self._file_handle and not self._file_handle.closed:
179
147
  try:
180
- # Release lock
181
148
  fcntl.flock(self._file_handle.fileno(), fcntl.LOCK_UN)
182
149
  self.logger.debug(f"Released lock on {self.path}")
183
150
  except OSError as e:
@@ -187,18 +154,15 @@ class LockedFileResource(AbstractFileResource):
187
154
 
188
155
  @property
189
156
  def file_handle(self) -> t.IO[str]:
190
- """Get the file handle."""
191
157
  if not self._file_handle:
192
158
  raise RuntimeError("LockedFileResource not initialized")
193
159
  return self._file_handle
194
160
 
195
161
  def read(self) -> str:
196
- """Read content from the locked file."""
197
162
  self.file_handle.seek(0)
198
163
  return self.file_handle.read()
199
164
 
200
165
  def write(self, content: str) -> None:
201
- """Write content to the locked file."""
202
166
  self.file_handle.seek(0)
203
167
  self.file_handle.write(content)
204
168
  self.file_handle.truncate()
@@ -207,8 +171,6 @@ class LockedFileResource(AbstractFileResource):
207
171
 
208
172
 
209
173
  class SafeDirectoryCreator(AbstractFileResource):
210
- """Safe directory creation with cleanup and rollback capabilities."""
211
-
212
174
  def __init__(
213
175
  self,
214
176
  path: Path,
@@ -224,18 +186,14 @@ class SafeDirectoryCreator(AbstractFileResource):
224
186
  manager.register_resource(self)
225
187
 
226
188
  async def _do_initialize(self) -> None:
227
- """Initialize safe directory creator."""
228
- # Track which directories we create
229
189
  current = self.path
230
190
 
231
191
  while not current.exists():
232
192
  self._created_dirs.append(current)
233
193
  current = current.parent
234
194
 
235
- # Reverse to create from parent to child
236
195
  self._created_dirs.reverse()
237
196
 
238
- # Create directories
239
197
  for dir_path in self._created_dirs:
240
198
  try:
241
199
  dir_path.mkdir(exist_ok=True)
@@ -247,13 +205,10 @@ class SafeDirectoryCreator(AbstractFileResource):
247
205
  raise
248
206
 
249
207
  async def _do_cleanup(self) -> None:
250
- """Clean up created directories if requested."""
251
208
  if self.cleanup_on_error:
252
209
  await self._cleanup_created_dirs()
253
210
 
254
211
  async def _cleanup_created_dirs(self) -> None:
255
- """Remove directories that we created."""
256
- # Remove in reverse order (child to parent)
257
212
  for dir_path in reversed(self._created_dirs):
258
213
  try:
259
214
  if dir_path.exists() and not any(dir_path.iterdir()):
@@ -264,8 +219,6 @@ class SafeDirectoryCreator(AbstractFileResource):
264
219
 
265
220
 
266
221
  class BatchFileOperations:
267
- """Batch file operations with atomic commit/rollback."""
268
-
269
222
  def __init__(self, manager: ResourceManager | None = None) -> None:
270
223
  self.manager = manager or ResourceManager()
271
224
  self.operations: list[t.Callable[[], None]] = []
@@ -278,15 +231,13 @@ class BatchFileOperations:
278
231
  content: str,
279
232
  backup: bool = True,
280
233
  ) -> None:
281
- """Add a write operation to the batch."""
282
-
283
- def write_op():
234
+ def write_op() -> None:
284
235
  writer = AtomicFileWriter(path, backup, self.manager)
285
236
  asyncio.create_task(writer.initialize())
286
237
  writer.write(content)
287
238
  asyncio.create_task(writer.commit())
288
239
 
289
- def rollback_op():
240
+ def rollback_op() -> None:
290
241
  writer = AtomicFileWriter(path, backup)
291
242
  asyncio.create_task(writer.rollback())
292
243
 
@@ -299,15 +250,13 @@ class BatchFileOperations:
299
250
  dest: Path,
300
251
  backup: bool = True,
301
252
  ) -> None:
302
- """Add a copy operation to the batch."""
303
-
304
- def copy_op():
253
+ def copy_op() -> None:
305
254
  if backup and dest.exists():
306
255
  backup_path = dest.with_suffix(f"{dest.suffix}.bak")
307
256
  shutil.copy2(dest, backup_path)
308
257
  shutil.copy2(source, dest)
309
258
 
310
- def rollback_op():
259
+ def rollback_op() -> None:
311
260
  if backup:
312
261
  backup_path = dest.with_suffix(f"{dest.suffix}.bak")
313
262
  if backup_path.exists():
@@ -321,12 +270,10 @@ class BatchFileOperations:
321
270
  source: Path,
322
271
  dest: Path,
323
272
  ) -> None:
324
- """Add a move operation to the batch."""
325
-
326
- def move_op():
273
+ def move_op() -> None:
327
274
  shutil.move(source, dest)
328
275
 
329
- def rollback_op():
276
+ def rollback_op() -> None:
330
277
  shutil.move(dest, source)
331
278
 
332
279
  self.operations.append(move_op)
@@ -337,10 +284,9 @@ class BatchFileOperations:
337
284
  path: Path,
338
285
  backup: bool = True,
339
286
  ) -> None:
340
- """Add a delete operation to the batch."""
341
287
  backup_path: Path | None = None
342
288
 
343
- def delete_op():
289
+ def delete_op() -> None:
344
290
  nonlocal backup_path
345
291
  if backup and path.exists():
346
292
  backup_path = path.with_suffix(f"{path.suffix}.bak.{os.getpid()}")
@@ -348,7 +294,7 @@ class BatchFileOperations:
348
294
  elif path.exists():
349
295
  path.unlink()
350
296
 
351
- def rollback_op():
297
+ def rollback_op() -> None:
352
298
  if backup_path and backup_path.exists():
353
299
  shutil.move(backup_path, path)
354
300
 
@@ -356,7 +302,6 @@ class BatchFileOperations:
356
302
  self.rollback_operations.append(rollback_op)
357
303
 
358
304
  async def commit_all(self) -> None:
359
- """Execute all operations atomically."""
360
305
  executed_ops = 0
361
306
 
362
307
  try:
@@ -369,7 +314,6 @@ class BatchFileOperations:
369
314
  except Exception as e:
370
315
  self.logger.error(f"Batch operation failed at step {executed_ops}: {e}")
371
316
 
372
- # Rollback executed operations in reverse order
373
317
  for i in range(executed_ops - 1, -1, -1):
374
318
  try:
375
319
  self.rollback_operations[i]()
@@ -381,13 +325,11 @@ class BatchFileOperations:
381
325
  raise RuntimeError("Batch file operations failed and rolled back") from e
382
326
 
383
327
 
384
- # Context managers for common file operations
385
328
  @contextlib.asynccontextmanager
386
329
  async def atomic_file_write(
387
330
  path: Path,
388
331
  backup: bool = True,
389
- ):
390
- """Context manager for atomic file writing."""
332
+ ) -> t.AsyncGenerator[AtomicFileWriter]:
391
333
  writer = AtomicFileWriter(path, backup)
392
334
  try:
393
335
  await writer.initialize()
@@ -405,8 +347,7 @@ async def locked_file_access(
405
347
  path: Path,
406
348
  mode: str = "r+",
407
349
  timeout: float = 30.0,
408
- ):
409
- """Context manager for locked file access."""
350
+ ) -> t.AsyncGenerator[LockedFileResource]:
410
351
  file_resource = LockedFileResource(path, mode, timeout)
411
352
  try:
412
353
  await file_resource.initialize()
@@ -419,8 +360,7 @@ async def locked_file_access(
419
360
  async def safe_directory_creation(
420
361
  path: Path,
421
362
  cleanup_on_error: bool = True,
422
- ):
423
- """Context manager for safe directory creation."""
363
+ ) -> t.AsyncGenerator[SafeDirectoryCreator]:
424
364
  creator = SafeDirectoryCreator(path, cleanup_on_error)
425
365
  try:
426
366
  await creator.initialize()
@@ -430,28 +370,22 @@ async def safe_directory_creation(
430
370
 
431
371
 
432
372
  @contextlib.asynccontextmanager
433
- async def batch_file_operations():
434
- """Context manager for batch file operations."""
373
+ async def batch_file_operations() -> t.AsyncGenerator[BatchFileOperations]:
435
374
  batch = BatchFileOperations()
436
375
  try:
437
376
  yield batch
438
377
  await batch.commit_all()
439
378
  except Exception:
440
- # Rollback is handled in commit_all
441
379
  raise
442
380
 
443
381
 
444
- # File operation utilities with enhanced error handling
445
382
  class SafeFileOperations:
446
- """Utility class for safe file operations with comprehensive error handling."""
447
-
448
383
  @staticmethod
449
384
  async def safe_read_text(
450
385
  path: Path,
451
386
  encoding: str = "utf-8",
452
387
  fallback_encodings: list[str] | None = None,
453
388
  ) -> str:
454
- """Safely read text file with encoding fallback."""
455
389
  fallback_encodings = fallback_encodings or ["latin-1", "cp1252"]
456
390
 
457
391
  for enc in [encoding] + fallback_encodings:
@@ -477,12 +411,10 @@ class SafeFileOperations:
477
411
  atomic: bool = True,
478
412
  backup: bool = True,
479
413
  ) -> None:
480
- """Safely write text file with atomic operation support."""
481
414
  if atomic:
482
415
  async with atomic_file_write(path, backup) as writer:
483
416
  writer.write(content)
484
417
  else:
485
- # Ensure parent directory exists
486
418
  path.parent.mkdir(parents=True, exist_ok=True)
487
419
  path.write_text(content, encoding=encoding)
488
420
 
@@ -493,17 +425,14 @@ class SafeFileOperations:
493
425
  preserve_metadata: bool = True,
494
426
  backup: bool = True,
495
427
  ) -> None:
496
- """Safely copy file with backup support."""
497
428
  if not source.exists():
498
429
  raise FileNotFoundError(f"Source file not found: {source}")
499
430
 
500
- # Create backup if requested
501
431
  if backup and dest.exists():
502
432
  backup_path = dest.with_suffix(f"{dest.suffix}.bak")
503
433
  shutil.copy2(dest, backup_path)
504
434
 
505
435
  try:
506
- # Ensure destination directory exists
507
436
  dest.parent.mkdir(parents=True, exist_ok=True)
508
437
 
509
438
  if preserve_metadata:
@@ -512,7 +441,6 @@ class SafeFileOperations:
512
441
  shutil.copy(source, dest)
513
442
 
514
443
  except Exception as e:
515
- # Restore backup if copy failed
516
444
  if backup and dest.with_suffix(f"{dest.suffix}.bak").exists():
517
445
  shutil.move(dest.with_suffix(f"{dest.suffix}.bak"), dest)
518
446
  raise RuntimeError(f"Failed to copy {source} to {dest}") from e
@@ -523,27 +451,22 @@ class SafeFileOperations:
523
451
  dest: Path,
524
452
  backup: bool = True,
525
453
  ) -> None:
526
- """Safely move file with backup support."""
527
454
  if not source.exists():
528
455
  raise FileNotFoundError(f"Source file not found: {source}")
529
456
 
530
- # Create backup of destination if it exists
531
457
  backup_path = None
532
458
  if backup and dest.exists():
533
459
  backup_path = dest.with_suffix(f"{dest.suffix}.bak.{os.getpid()}")
534
460
  shutil.move(dest, backup_path)
535
461
 
536
462
  try:
537
- # Ensure destination directory exists
538
463
  dest.parent.mkdir(parents=True, exist_ok=True)
539
464
  shutil.move(source, dest)
540
465
 
541
- # Remove backup on success
542
466
  if backup_path and backup_path.exists():
543
467
  backup_path.unlink()
544
468
 
545
469
  except Exception as e:
546
- # Restore backup if move failed
547
470
  if backup_path and backup_path.exists():
548
471
  shutil.move(backup_path, dest)
549
472
  raise RuntimeError(f"Failed to move {source} to {dest}") from e
@@ -142,13 +142,13 @@ class OptimizedFileWatcher:
142
142
 
143
143
  @memoize_with_ttl(ttl=30.0)
144
144
  def get_python_files(self) -> list[Path]:
145
- return list(self.root_path.rglob("*.py"))
145
+ return list[t.Any](self.root_path.rglob("*.py"))
146
146
 
147
147
  def get_modified_files(self, since: float) -> list[Path]:
148
148
  cache_key = f"modified_since_{since}"
149
149
  cached = self._file_cache.get(cache_key)
150
150
  if cached is not None:
151
- return cached
151
+ return t.cast(list[Path], cached)
152
152
  modified_files: list[Path] = []
153
153
  for py_file in self.get_python_files():
154
154
  try: