crackerjack 0.31.10__py3-none-any.whl โ†’ 0.31.13__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 +50 -9
  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.13.dist-info}/METADATA +197 -12
  150. crackerjack-0.31.13.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.13.dist-info}/WHEEL +0 -0
  154. {crackerjack-0.31.10.dist-info โ†’ crackerjack-0.31.13.dist-info}/entry_points.txt +0 -0
  155. {crackerjack-0.31.10.dist-info โ†’ crackerjack-0.31.13.dist-info}/licenses/LICENSE +0 -0
@@ -1,9 +1,3 @@
1
- """Test execution engine with progress tracking and output parsing.
2
-
3
- This module handles the actual test execution, subprocess management, and real-time
4
- output parsing. Split from test_manager.py for better separation of concerns.
5
- """
6
-
7
1
  import re
8
2
  import subprocess
9
3
  import threading
@@ -18,8 +12,6 @@ from .test_progress import TestProgress
18
12
 
19
13
 
20
14
  class TestExecutor:
21
- """Handles test execution with real-time progress tracking."""
22
-
23
15
  def __init__(self, console: Console, pkg_path: Path) -> None:
24
16
  self.console = console
25
17
  self.pkg_path = pkg_path
@@ -29,7 +21,6 @@ class TestExecutor:
29
21
  cmd: list[str],
30
22
  timeout: int = 600,
31
23
  ) -> subprocess.CompletedProcess[str]:
32
- """Execute test command with live progress display."""
33
24
  return self._execute_with_live_progress(cmd, timeout)
34
25
 
35
26
  def execute_with_ai_progress(
@@ -38,13 +29,11 @@ class TestExecutor:
38
29
  progress_callback: t.Callable[[dict[str, t.Any]], None],
39
30
  timeout: int = 600,
40
31
  ) -> subprocess.CompletedProcess[str]:
41
- """Execute test command with AI-compatible progress callbacks."""
42
32
  return self._run_test_command_with_ai_progress(cmd, progress_callback, timeout)
43
33
 
44
34
  def _execute_with_live_progress(
45
35
  self, cmd: list[str], timeout: int
46
36
  ) -> subprocess.CompletedProcess[str]:
47
- """Execute tests with Rich live progress display."""
48
37
  progress = self._initialize_progress()
49
38
 
50
39
  with Live(progress.format_progress(), console=self.console) as live:
@@ -61,12 +50,10 @@ class TestExecutor:
61
50
  env=env,
62
51
  )
63
52
 
64
- # Start reader threads
65
53
  stdout_thread, stderr_thread, monitor_thread = self._start_reader_threads(
66
54
  process, progress, live
67
55
  )
68
56
 
69
- # Wait for completion
70
57
  try:
71
58
  process.wait(timeout=timeout)
72
59
  except subprocess.TimeoutExpired:
@@ -74,10 +61,8 @@ class TestExecutor:
74
61
  process, progress, "Test execution timed out"
75
62
  )
76
63
 
77
- # Cleanup
78
64
  self._cleanup_threads([stdout_thread, stderr_thread, monitor_thread])
79
65
 
80
- # Return completed process with string output
81
66
  stdout_str = process.stdout.read() if process.stdout else ""
82
67
  stderr_str = process.stderr.read() if process.stderr else ""
83
68
  return subprocess.CompletedProcess(
@@ -90,7 +75,6 @@ class TestExecutor:
90
75
  progress_callback: t.Callable[[dict[str, t.Any]], None],
91
76
  timeout: int = 600,
92
77
  ) -> subprocess.CompletedProcess[str]:
93
- """Execute test command with AI progress callbacks."""
94
78
  progress = self._initialize_progress()
95
79
  env = self._setup_coverage_env()
96
80
 
@@ -101,13 +85,11 @@ class TestExecutor:
101
85
  return result
102
86
 
103
87
  def _initialize_progress(self) -> TestProgress:
104
- """Initialize progress tracker."""
105
88
  progress = TestProgress()
106
89
  progress.start_time = time.time()
107
90
  return progress
108
91
 
109
92
  def _setup_test_environment(self) -> dict[str, str]:
110
- """Set up environment variables for test execution."""
111
93
  import os
112
94
 
113
95
  cache_dir = Path.home() / ".cache" / "crackerjack" / "coverage"
@@ -119,7 +101,6 @@ class TestExecutor:
119
101
  return env
120
102
 
121
103
  def _setup_coverage_env(self) -> dict[str, str]:
122
- """Set up coverage environment for AI mode."""
123
104
  import os
124
105
  from pathlib import Path
125
106
 
@@ -133,7 +114,6 @@ class TestExecutor:
133
114
  def _start_reader_threads(
134
115
  self, process: subprocess.Popen[str], progress: TestProgress, live: Live
135
116
  ) -> tuple[threading.Thread, threading.Thread, threading.Thread]:
136
- """Start threads for reading stdout, stderr, and monitoring."""
137
117
  stdout_thread = self._create_stdout_reader(process, progress, live)
138
118
  stderr_thread = self._create_stderr_reader(process, progress, live)
139
119
  monitor_thread = self._create_monitor_thread(progress)
@@ -147,8 +127,6 @@ class TestExecutor:
147
127
  def _create_stdout_reader(
148
128
  self, process: subprocess.Popen[str], progress: TestProgress, live: Live
149
129
  ) -> threading.Thread:
150
- """Create thread for reading stdout."""
151
-
152
130
  def read_output() -> None:
153
131
  if process.stdout:
154
132
  for line in iter(process.stdout.readline, ""):
@@ -163,8 +141,6 @@ class TestExecutor:
163
141
  def _create_stderr_reader(
164
142
  self, process: subprocess.Popen[str], progress: TestProgress, live: Live
165
143
  ) -> threading.Thread:
166
- """Create thread for reading stderr."""
167
-
168
144
  def read_stderr() -> None:
169
145
  if process.stderr:
170
146
  for line in iter(process.stderr.readline, ""):
@@ -175,8 +151,6 @@ class TestExecutor:
175
151
  return threading.Thread(target=read_stderr, daemon=True)
176
152
 
177
153
  def _create_monitor_thread(self, progress: TestProgress) -> threading.Thread:
178
- """Create thread for monitoring stuck tests."""
179
-
180
154
  def monitor_stuck_tests() -> None:
181
155
  last_update = time.time()
182
156
  last_test = ""
@@ -198,31 +172,26 @@ class TestExecutor:
198
172
  return threading.Thread(target=monitor_stuck_tests, daemon=True)
199
173
 
200
174
  def _process_test_output_line(self, line: str, progress: TestProgress) -> None:
201
- """Process a single line of test output."""
202
175
  self._parse_test_line(line, progress)
203
176
 
204
177
  def _parse_test_line(self, line: str, progress: TestProgress) -> None:
205
- """Parse test output line and update progress."""
206
- # Handle collection completion
207
178
  if self._handle_collection_completion(line, progress):
208
179
  return
209
180
 
210
- # Handle session events
211
181
  if self._handle_session_events(line, progress):
212
182
  return
213
183
 
214
- # Handle collection progress
215
184
  if self._handle_collection_progress(line, progress):
216
185
  return
217
186
 
218
- # Handle test execution
219
187
  if self._handle_test_execution(line, progress):
220
188
  return
221
189
 
222
190
  def _handle_collection_completion(self, line: str, progress: TestProgress) -> bool:
223
- """Handle test collection completion."""
224
191
  if "collected" in line and ("item" in line or "test" in line):
225
- match = re.search(r"(\d+) (?:item|test)", line)
192
+ match = re.search(
193
+ r"(\d +) (?: item | test)", line
194
+ ) # REGEX OK: parsing pytest output format
226
195
  if match:
227
196
  progress.update(
228
197
  total_tests=int(match.group(1)),
@@ -233,7 +202,6 @@ class TestExecutor:
233
202
  return False
234
203
 
235
204
  def _handle_session_events(self, line: str, progress: TestProgress) -> bool:
236
- """Handle pytest session events."""
237
205
  if "session starts" in line and progress.collection_status != "Session started":
238
206
  progress.update(collection_status="Session started")
239
207
  return True
@@ -246,9 +214,7 @@ class TestExecutor:
246
214
  return False
247
215
 
248
216
  def _handle_collection_progress(self, line: str, progress: TestProgress) -> bool:
249
- """Handle collection progress updates."""
250
217
  if progress.is_collecting:
251
- # Look for file discovery patterns
252
218
  if line.endswith(".py") and ("test_" in line or "_test.py" in line):
253
219
  with progress._lock:
254
220
  if line not in progress._seen_files:
@@ -261,8 +227,6 @@ class TestExecutor:
261
227
  return False
262
228
 
263
229
  def _handle_test_execution(self, line: str, progress: TestProgress) -> bool:
264
- """Handle test execution progress."""
265
- # Test result patterns
266
230
  if " PASSED " in line:
267
231
  progress.update(passed=progress.passed + 1)
268
232
  self._extract_current_test(line, progress)
@@ -279,39 +243,32 @@ class TestExecutor:
279
243
  progress.update(errors=progress.errors + 1)
280
244
  self._extract_current_test(line, progress)
281
245
  return True
282
- elif "::" in line and any(x in line for x in ("RUNNING", "test_")):
246
+ elif ":: " in line and any(x in line for x in ("RUNNING", "test_")):
283
247
  self._handle_running_test(line, progress)
284
248
  return True
285
249
 
286
250
  return False
287
251
 
288
252
  def _handle_running_test(self, line: str, progress: TestProgress) -> None:
289
- """Handle currently running test indicator."""
290
- if "::" in line:
291
- # Extract test name from line
292
- test_parts = line.split("::")
253
+ if ":: " in line:
254
+ test_parts = line.split(":: ")
293
255
  if len(test_parts) >= 2:
294
- test_name = "::".join(test_parts[-2:])
256
+ test_name = ":: ".join(test_parts[-2:])
295
257
  progress.update(current_test=test_name)
296
258
 
297
259
  def _extract_current_test(self, line: str, progress: TestProgress) -> None:
298
- """Extract current test name from output line."""
299
- if "::" in line:
300
- # Extract test identifier
260
+ if ":: " in line:
301
261
  parts = line.split(" ")
302
262
  for part in parts:
303
- if "::" in part:
263
+ if ":: " in part:
304
264
  progress.update(current_test=part)
305
265
  break
306
266
 
307
267
  def _update_display_if_needed(self, progress: TestProgress, live: Live) -> None:
308
- """Update display if enough time has passed or significant change occurred."""
309
268
  if self._should_refresh_display(progress):
310
269
  live.update(progress.format_progress())
311
270
 
312
271
  def _should_refresh_display(self, progress: TestProgress) -> bool:
313
- """Determine if display should be refreshed."""
314
- # Only refresh on significant changes to reduce spam
315
272
  return (
316
273
  progress.is_complete
317
274
  or progress.total_tests > 0
@@ -319,12 +276,10 @@ class TestExecutor:
319
276
  )
320
277
 
321
278
  def _mark_test_as_stuck(self, progress: TestProgress, test_name: str) -> None:
322
- """Mark a test as potentially stuck."""
323
279
  if test_name:
324
280
  progress.update(current_test=f"๐ŸŒ {test_name} (slow)")
325
281
 
326
282
  def _cleanup_threads(self, threads: list[threading.Thread]) -> None:
327
- """Clean up reader threads."""
328
283
  for thread in threads:
329
284
  if thread.is_alive():
330
285
  thread.join(timeout=1.0)
@@ -332,7 +287,6 @@ class TestExecutor:
332
287
  def _handle_progress_error(
333
288
  self, process: subprocess.Popen[str], progress: TestProgress, error_msg: str
334
289
  ) -> None:
335
- """Handle progress tracking errors."""
336
290
  process.terminate()
337
291
  progress.update(is_complete=True, current_test=f"โŒ {error_msg}")
338
292
 
@@ -344,7 +298,6 @@ class TestExecutor:
344
298
  progress_callback: t.Callable[[dict[str, t.Any]], None],
345
299
  timeout: int,
346
300
  ) -> subprocess.CompletedProcess[str]:
347
- """Execute test process with AI progress tracking."""
348
301
  process = subprocess.Popen(
349
302
  cmd,
350
303
  cwd=self.pkg_path,
@@ -371,7 +324,6 @@ class TestExecutor:
371
324
  progress: TestProgress,
372
325
  progress_callback: t.Callable[[dict[str, t.Any]], None],
373
326
  ) -> list[str]:
374
- """Read stdout with progress updates."""
375
327
  stdout_lines = []
376
328
 
377
329
  if process.stdout:
@@ -388,7 +340,6 @@ class TestExecutor:
388
340
  return stdout_lines
389
341
 
390
342
  def _read_stderr_lines(self, process: subprocess.Popen[str]) -> list[str]:
391
- """Read stderr lines."""
392
343
  stderr_lines = []
393
344
 
394
345
  if process.stderr:
@@ -404,7 +355,6 @@ class TestExecutor:
404
355
  def _wait_for_process_completion(
405
356
  self, process: subprocess.Popen[str], timeout: int
406
357
  ) -> int:
407
- """Wait for process completion with timeout."""
408
358
  try:
409
359
  process.wait(timeout=timeout)
410
360
  return process.returncode
@@ -417,7 +367,6 @@ class TestExecutor:
417
367
  progress: TestProgress,
418
368
  progress_callback: t.Callable[[dict[str, t.Any]], None],
419
369
  ) -> None:
420
- """Emit progress update for AI consumption."""
421
370
  progress_data = {
422
371
  "type": "test_progress",
423
372
  "total_tests": progress.total_tests,
@@ -439,5 +388,4 @@ class TestExecutor:
439
388
  from contextlib import suppress
440
389
 
441
390
  with suppress(Exception):
442
- # Don't let progress callback errors affect test execution
443
391
  progress_callback(progress_data)
@@ -1,15 +1,3 @@
1
- """Refactored test manager with focused responsibilities.
2
-
3
- This is the new modular test manager that delegates to specialized components:
4
- - TestExecutor: Handles test execution and progress tracking
5
- - TestCommandBuilder: Builds pytest commands with appropriate options
6
- - TestProgress: Tracks and displays test progress
7
- - CoverageRatchetService: Manages coverage requirements
8
-
9
- REFACTORING NOTE: Original test_manager.py was 1133 lines with 60+ methods.
10
- This refactored version is ~200 lines and delegates to focused modules.
11
- """
12
-
13
1
  import subprocess
14
2
  import time
15
3
  import typing as t
@@ -25,18 +13,14 @@ from .test_executor import TestExecutor
25
13
 
26
14
 
27
15
  class TestManager:
28
- """Refactored test manager with modular architecture."""
29
-
30
16
  def __init__(self, console: Console, pkg_path: Path) -> None:
31
17
  self.console = console
32
18
  self.pkg_path = pkg_path
33
19
 
34
- # Initialize specialized components
35
20
  self.executor = TestExecutor(console, pkg_path)
36
21
  self.command_builder = TestCommandBuilder(pkg_path)
37
22
  self.coverage_ratchet = CoverageRatchetService(pkg_path, console)
38
23
 
39
- # State
40
24
  self._last_test_failures: list[str] = []
41
25
  self._progress_callback: t.Callable[[dict[str, t.Any]], None] | None = None
42
26
  self.coverage_ratchet_enabled = True
@@ -45,21 +29,18 @@ class TestManager:
45
29
  self,
46
30
  callback: t.Callable[[dict[str, t.Any]], None] | None,
47
31
  ) -> None:
48
- """Set callback for AI mode structured progress updates."""
49
32
  self._progress_callback = callback
50
33
 
51
34
  def set_coverage_ratchet_enabled(self, enabled: bool) -> None:
52
- """Enable or disable the coverage ratchet system."""
53
35
  self.coverage_ratchet_enabled = enabled
54
36
  if enabled:
55
37
  self.console.print(
56
- "[cyan]๐Ÿ“Š[/cyan] Coverage ratchet enabled - targeting 100% coverage"
38
+ "[cyan]๐Ÿ“Š[/ cyan] Coverage ratchet enabled-targeting 100 % coverage"
57
39
  )
58
40
  else:
59
- self.console.print("[yellow]โš ๏ธ[/yellow] Coverage ratchet disabled")
41
+ self.console.print("[yellow]โš ๏ธ[/ yellow] Coverage ratchet disabled")
60
42
 
61
43
  def run_tests(self, options: OptionsProtocol) -> bool:
62
- """Run tests with comprehensive progress tracking and coverage analysis."""
63
44
  start_time = time.time()
64
45
 
65
46
  try:
@@ -77,44 +58,39 @@ class TestManager:
77
58
  return self._handle_test_error(start_time, e)
78
59
 
79
60
  def run_specific_tests(self, test_pattern: str) -> bool:
80
- """Run tests matching a specific pattern."""
81
- self.console.print(f"[cyan]๐Ÿงช[/cyan] Running tests matching: {test_pattern}")
61
+ self.console.print(f"[cyan]๐Ÿงช[/ cyan] Running tests matching: {test_pattern}")
82
62
 
83
63
  cmd = self.command_builder.build_specific_test_command(test_pattern)
84
64
  result = self.executor.execute_with_progress(cmd)
85
65
 
86
66
  success = result.returncode == 0
87
67
  if success:
88
- self.console.print("[green]โœ…[/green] Specific tests passed")
68
+ self.console.print("[green]โœ…[/ green] Specific tests passed")
89
69
  else:
90
- self.console.print("[red]โŒ[/red] Some specific tests failed")
70
+ self.console.print("[red]โŒ[/ red] Some specific tests failed")
91
71
 
92
72
  return success
93
73
 
94
74
  def validate_test_environment(self) -> bool:
95
- """Validate test environment and configuration."""
96
75
  if not self.has_tests():
97
- self.console.print("[yellow]โš ๏ธ[/yellow] No tests found")
76
+ self.console.print("[yellow]โš ๏ธ[/ yellow] No tests found")
98
77
  return False
99
78
 
100
- # Test basic pytest collection
101
79
  cmd = self.command_builder.build_validation_command()
102
80
  result = subprocess.run(cmd, cwd=self.pkg_path, capture_output=True, text=True)
103
81
 
104
82
  if result.returncode != 0:
105
- self.console.print("[red]โŒ[/red] Test environment validation failed")
83
+ self.console.print("[red]โŒ[/ red] Test environment validation failed")
106
84
  self.console.print(result.stderr)
107
85
  return False
108
86
 
109
- self.console.print("[green]โœ…[/green] Test environment validated")
87
+ self.console.print("[green]โœ…[/ green] Test environment validated")
110
88
  return True
111
89
 
112
90
  def get_coverage_ratchet_status(self) -> dict[str, t.Any]:
113
- """Get comprehensive coverage ratchet status."""
114
91
  return self.coverage_ratchet.get_status_report()
115
92
 
116
93
  def get_test_stats(self) -> dict[str, t.Any]:
117
- """Get comprehensive test execution statistics."""
118
94
  return {
119
95
  "has_tests": self.has_tests(),
120
96
  "coverage_ratchet_enabled": self.coverage_ratchet_enabled,
@@ -122,24 +98,19 @@ class TestManager:
122
98
  }
123
99
 
124
100
  def get_test_failures(self) -> list[str]:
125
- """Get list of recent test failures."""
126
101
  return self._last_test_failures.copy()
127
102
 
128
103
  def get_test_command(self, options: OptionsProtocol) -> list[str]:
129
- """Get the test command that would be executed."""
130
104
  return self.command_builder.build_command(options)
131
105
 
132
106
  def get_coverage_report(self) -> str | None:
133
- """Get coverage report if available."""
134
107
  try:
135
108
  return self.coverage_ratchet.get_coverage_report()
136
109
  except Exception:
137
110
  return None
138
111
 
139
112
  def get_coverage(self) -> dict[str, t.Any]:
140
- """Get coverage information as required by TestManagerProtocol."""
141
113
  try:
142
- # Get the ratchet status which includes coverage information
143
114
  status = self.coverage_ratchet.get_status_report()
144
115
 
145
116
  if status.get("status") == "not_initialized":
@@ -167,7 +138,6 @@ class TestManager:
167
138
  }
168
139
 
169
140
  def has_tests(self) -> bool:
170
- """Check if project has tests."""
171
141
  test_directories = ["tests", "test"]
172
142
  test_files = ["test_*.py", "*_test.py"]
173
143
 
@@ -178,19 +148,15 @@ class TestManager:
178
148
  if list(test_path.glob(f"**/{test_file_pattern}")):
179
149
  return True
180
150
 
181
- # Check for test files in root directory
182
151
  for test_file_pattern in test_files:
183
152
  if list(self.pkg_path.glob(test_file_pattern)):
184
153
  return True
185
154
 
186
155
  return False
187
156
 
188
- # Private implementation methods
189
-
190
157
  def _execute_test_workflow(
191
158
  self, options: OptionsProtocol
192
159
  ) -> subprocess.CompletedProcess[str]:
193
- """Execute the complete test workflow."""
194
160
  self._print_test_start_message(options)
195
161
 
196
162
  cmd = self.command_builder.build_command(options)
@@ -202,17 +168,15 @@ class TestManager:
202
168
  return self.executor.execute_with_progress(cmd, self._get_timeout(options))
203
169
 
204
170
  def _print_test_start_message(self, options: OptionsProtocol) -> None:
205
- """Print test execution start message."""
206
171
  workers = self.command_builder.get_optimal_workers(options)
207
172
  timeout = self.command_builder.get_test_timeout(options)
208
173
 
209
174
  self.console.print(
210
- f"[cyan]๐Ÿงช[/cyan] Running tests (workers: {workers}, timeout: {timeout}s)"
175
+ f"[cyan]๐Ÿงช[/ cyan] Running tests (workers: {workers}, timeout: {timeout}s)"
211
176
  )
212
177
 
213
178
  def _handle_test_success(self, output: str, duration: float) -> bool:
214
- """Handle successful test execution."""
215
- self.console.print(f"[green]โœ…[/green] Tests passed in {duration:.1f}s")
179
+ self.console.print(f"[green]โœ…[/ green] Tests passed in {duration: .1f}s")
216
180
 
217
181
  if self.coverage_ratchet_enabled:
218
182
  return self._process_coverage_ratchet()
@@ -220,22 +184,19 @@ class TestManager:
220
184
  return True
221
185
 
222
186
  def _handle_test_failure(self, output: str, duration: float) -> bool:
223
- """Handle failed test execution."""
224
- self.console.print(f"[red]โŒ[/red] Tests failed in {duration:.1f}s")
187
+ self.console.print(f"[red]โŒ[/ red] Tests failed in {duration: .1f}s")
225
188
 
226
189
  self._last_test_failures = self._extract_failure_lines(output)
227
190
  return False
228
191
 
229
192
  def _handle_test_error(self, start_time: float, error: Exception) -> bool:
230
- """Handle test execution errors."""
231
193
  duration = time.time() - start_time
232
194
  self.console.print(
233
- f"[red]๐Ÿ’ฅ[/red] Test execution error after {duration:.1f}s: {error}"
195
+ f"[red]๐Ÿ’ฅ[/ red] Test execution error after {duration: .1f}s: {error}"
234
196
  )
235
197
  return False
236
198
 
237
199
  def _process_coverage_ratchet(self) -> bool:
238
- """Process coverage ratchet checks."""
239
200
  if not self.coverage_ratchet_enabled:
240
201
  return True
241
202
 
@@ -243,37 +204,32 @@ class TestManager:
243
204
  return self._handle_ratchet_result(ratchet_result)
244
205
 
245
206
  def _handle_ratchet_result(self, ratchet_result: dict[str, t.Any]) -> bool:
246
- """Handle coverage ratchet results."""
247
207
  if ratchet_result.get("success", False):
248
208
  if ratchet_result.get("improved", False):
249
209
  self._handle_coverage_improvement(ratchet_result)
250
210
  return True
251
211
  else:
252
- # Use the message from the ratchet result if available, or construct from available data
253
212
  if "message" in ratchet_result:
254
- self.console.print(f"[red]๐Ÿ“‰[/red] {ratchet_result['message']}")
213
+ self.console.print(f"[red]๐Ÿ“‰[/ red] {ratchet_result['message']}")
255
214
  else:
256
- # Fallback to constructing message from available keys
257
215
  current = ratchet_result.get("current_coverage", 0)
258
216
  previous = ratchet_result.get("previous_coverage", 0)
259
217
  self.console.print(
260
- f"[red]๐Ÿ“‰[/red] Coverage regression: "
218
+ f"[red]๐Ÿ“‰[/ red] Coverage regression: "
261
219
  f"{current:.2f}% < {previous:.2f}%"
262
220
  )
263
221
  return False
264
222
 
265
223
  def _handle_coverage_improvement(self, ratchet_result: dict[str, t.Any]) -> None:
266
- """Handle coverage improvements."""
267
224
  improvement = ratchet_result.get("improvement", 0)
268
225
  current = ratchet_result.get("current_coverage", 0)
269
226
 
270
227
  self.console.print(
271
- f"[green]๐Ÿ“ˆ[/green] Coverage improved by {improvement:.2f}% "
272
- f"to {current:.2f}%"
228
+ f"[green]๐Ÿ“ˆ[/ green] Coverage improved by {improvement: .2f}% "
229
+ f"to {current: .2f}%"
273
230
  )
274
231
 
275
232
  def _extract_failure_lines(self, output: str) -> list[str]:
276
- """Extract failure information from test output."""
277
233
  failures = []
278
234
  lines = output.split("\n")
279
235
 
@@ -283,12 +239,10 @@ class TestManager:
283
239
  ):
284
240
  failures.append(line.strip())
285
241
 
286
- return failures[:10] # Limit to first 10 failures
242
+ return failures[:10]
287
243
 
288
244
  def _get_timeout(self, options: OptionsProtocol) -> int:
289
- """Get timeout for test execution."""
290
245
  return self.command_builder.get_test_timeout(options)
291
246
 
292
247
 
293
- # For backward compatibility, also export the old class name
294
248
  TestManagementImpl = TestManager