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
@@ -30,8 +30,8 @@ class StreamingMode(str, Enum):
30
30
 
31
31
 
32
32
  class AICoordinationMode(str, Enum):
33
- SINGLE_AGENT = "single - agent"
34
- MULTI_AGENT = "multi - agent"
33
+ SINGLE_AGENT = "single-agent"
34
+ MULTI_AGENT = "multi-agent"
35
35
  COORDINATOR = "coordinator"
36
36
 
37
37
 
@@ -138,13 +138,13 @@ class StrategySelector:
138
138
 
139
139
  if len(context.previous_failures) > 5:
140
140
  self.console.print(
141
- "[yellow]🧠 Switching to individual execution due to multiple failures[/yellow]",
141
+ "[yellow]🧠 Switching to individual execution due to multiple failures[/ yellow]",
142
142
  )
143
143
  return ExecutionStrategy.INDIVIDUAL
144
144
 
145
145
  if context.changed_files and len(context.changed_files) < 5:
146
146
  self.console.print(
147
- "[cyan]🎯 Using selective execution for targeted file changes[/cyan]",
147
+ "[cyan]🎯 Using selective execution for targeted file changes[/ cyan]",
148
148
  )
149
149
  return ExecutionStrategy.SELECTIVE
150
150
 
@@ -174,11 +174,11 @@ class StrategySelector:
174
174
  if context.changed_files:
175
175
  for file_path in context.changed_files:
176
176
  if file_path.suffix == ".py":
177
- priority_hooks.update(["pyright", "ruff - check", "ruff - format"])
177
+ priority_hooks.update(["pyright", "ruff-check", "ruff-format"])
178
178
  if "test" in str(file_path):
179
179
  priority_hooks.update(["pytest", "coverage"])
180
180
  if str(file_path).endswith(("setup.py", "pyproject.toml")):
181
- priority_hooks.update(["bandit", "creosote", "detect - secrets"])
181
+ priority_hooks.update(["bandit", "creosote", "detect-secrets"])
182
182
 
183
183
  selected_hooks = [
184
184
  hook for hook in strategy.hooks if hook.name in priority_hooks
@@ -189,7 +189,7 @@ class StrategySelector:
189
189
 
190
190
  self.console.print(
191
191
  f"[cyan]🎯 Selected {len(selected_hooks)} hooks for targeted execution: "
192
- f"{', '.join(h.name for h in selected_hooks)}[/cyan]",
192
+ f"{', '.join(h.name for h in selected_hooks)}[/ cyan]",
193
193
  )
194
194
 
195
195
  return HookStrategy(
@@ -303,35 +303,35 @@ class ExecutionPlan:
303
303
  def print_plan_summary(self, console: Console) -> None:
304
304
  console.print("\n" + "=" * 80)
305
305
  console.print(
306
- "[bold bright_blue]🎯 ORCHESTRATED EXECUTION PLAN[/bold bright_blue]",
306
+ "[bold bright_blue]🎯 ORCHESTRATED EXECUTION PLAN[/ bold bright_blue]",
307
307
  )
308
308
  console.print("=" * 80)
309
309
 
310
- console.print(f"[bold]Strategy: [/bold] {self.execution_strategy.value}")
310
+ console.print(f"[bold]Strategy: [/ bold] {self.execution_strategy.value}")
311
311
  console.print(
312
- f"[bold]AI Mode: [/bold] {self.config.ai_coordination_mode.value}",
312
+ f"[bold]AI Mode: [/ bold] {self.config.ai_coordination_mode.value}",
313
313
  )
314
314
  console.print(
315
- f"[bold]Progress Level: [/bold] {self.config.progress_level.value}",
315
+ f"[bold]Progress Level: [/ bold] {self.config.progress_level.value}",
316
316
  )
317
317
  console.print(
318
- f"[bold]Estimated Duration: [/bold] {self.estimated_total_duration: .1f}s",
318
+ f"[bold]Estimated Duration: [/ bold] {self.estimated_total_duration: .1f}s",
319
319
  )
320
320
 
321
- console.print("\n[bold cyan]Hook Execution: [/bold cyan]")
321
+ console.print("\n[bold cyan]Hook Execution: [/ bold cyan]")
322
322
  for i, plan in enumerate(self.hook_plans, 1):
323
323
  strategy = plan["strategy"]
324
324
  console.print(
325
- f" {i}. {strategy.name} - {len(strategy.hooks)} hooks "
325
+ f" {i}. {strategy.name}-{len(strategy.hooks)} hooks "
326
326
  f"({plan['estimated_duration']: .1f}s)",
327
327
  )
328
328
 
329
- console.print("\n[bold green]Test Execution: [/bold green]")
329
+ console.print("\n[bold green]Test Execution: [/ bold green]")
330
330
  console.print(f" Mode: {self.test_plan['mode']}")
331
331
  console.print(f" Workers: {self.test_plan['parallel_workers']}")
332
332
  console.print(f" Duration: {self.test_plan['estimated_duration']: .1f}s")
333
333
 
334
- console.print("\n[bold magenta]AI Coordination: [/bold magenta]")
334
+ console.print("\n[bold magenta]AI Coordination: [/ bold magenta]")
335
335
  console.print(f" Mode: {self.ai_plan['mode'].value}")
336
336
  console.print(f" Intelligence: {self.ai_plan['intelligence_level'].value}")
337
337
  console.print(
@@ -1,5 +1,4 @@
1
1
  import asyncio
2
- import re
3
2
  import subprocess
4
3
  import time
5
4
  import typing as t
@@ -9,6 +8,7 @@ from pathlib import Path
9
8
  from rich.console import Console
10
9
 
11
10
  from crackerjack.models.protocols import OptionsProtocol
11
+ from crackerjack.services.regex_patterns import SAFE_PATTERNS, CompiledPatternCache
12
12
 
13
13
 
14
14
  @dataclass
@@ -104,25 +104,31 @@ class TestSuiteProgress:
104
104
 
105
105
 
106
106
  class PytestOutputParser:
107
- TEST_START_PATTERN = re.compile(
108
- r"^(.+?)::(.*)::(.*)(?:PASSED|FAILED|SKIPPED|ERROR)",
109
- )
110
- TEST_RESULT_PATTERN = re.compile(
111
- r"^(.+?)(?:PASSED|FAILED|SKIPPED|ERROR)(?:\s+\[.*?\])?\s*$",
112
- )
113
- TEST_COLLECTION_PATTERN = re.compile(r"collected (\d+) items?")
114
- TEST_SESSION_START = re.compile(r"test session starts")
115
- COVERAGE_PATTERN = re.compile(r"TOTAL\s+\d+\s+\d+\s+(\d+)%")
116
-
117
- DETAILED_TEST_PATTERN = re.compile(
118
- r"^(.+?\.py)::(.*)(?:PASSED|FAILED|SKIPPED|ERROR)(?:\s+\[(\d+)%\])?\s*(?:\[(.*?)\])?\s*$",
119
- )
120
-
121
107
  def __init__(self) -> None:
122
108
  self.current_test: str | None = None
123
109
  self.test_traceback_buffer: list[str] = []
124
110
  self.in_traceback = False
125
111
 
112
+ # Cache compiled patterns for optimal performance during test parsing
113
+ self._test_start_pattern = CompiledPatternCache.get_compiled_pattern(
114
+ SAFE_PATTERNS["pytest_test_start"].pattern
115
+ )
116
+ self._test_result_pattern = CompiledPatternCache.get_compiled_pattern(
117
+ SAFE_PATTERNS["pytest_test_result"].pattern
118
+ )
119
+ self._test_collection_pattern = CompiledPatternCache.get_compiled_pattern(
120
+ SAFE_PATTERNS["pytest_collection_count"].pattern
121
+ )
122
+ self._session_start_pattern = CompiledPatternCache.get_compiled_pattern(
123
+ SAFE_PATTERNS["pytest_session_start"].pattern
124
+ )
125
+ self._coverage_pattern = CompiledPatternCache.get_compiled_pattern(
126
+ SAFE_PATTERNS["pytest_coverage_total"].pattern
127
+ )
128
+ self._detailed_test_pattern = CompiledPatternCache.get_compiled_pattern(
129
+ SAFE_PATTERNS["pytest_detailed_test"].pattern
130
+ )
131
+
126
132
  def parse_pytest_output(self, output_lines: list[str]) -> dict[str, t.Any]:
127
133
  tests: dict[str, TestProgress] = {}
128
134
  suite_info = TestSuiteProgress()
@@ -148,7 +154,7 @@ class PytestOutputParser:
148
154
  line: str,
149
155
  suite_info: TestSuiteProgress,
150
156
  ) -> None:
151
- if match := self.TEST_COLLECTION_PATTERN.search(line):
157
+ if match := self._test_collection_pattern.search(line):
152
158
  suite_info.total_tests = int(match.group(1))
153
159
 
154
160
  def _process_test_result_line(
@@ -157,9 +163,9 @@ class PytestOutputParser:
157
163
  tests: dict[str, TestProgress],
158
164
  suite_info: TestSuiteProgress,
159
165
  ) -> None:
160
- if match := self.DETAILED_TEST_PATTERN.match(line):
161
- file_path, test_name, status, _progress, _timing = match.groups()
162
- test_id = f"{file_path}::{test_name}"
166
+ if match := self._detailed_test_pattern.match(line):
167
+ file_path, test_name, status = match.groups()
168
+ test_id = f"{file_path}:: {test_name}"
163
169
 
164
170
  if test_id not in tests:
165
171
  tests[test_id] = self._create_test_progress(
@@ -178,7 +184,7 @@ class PytestOutputParser:
178
184
  test_id: str,
179
185
  ) -> TestProgress:
180
186
  test_file = Path(file_path).name
181
- test_parts = test_name.split("::")
187
+ test_parts = test_name.split(":: ")
182
188
  test_class = test_parts[0] if len(test_parts) > 1 else None
183
189
  test_method = test_parts[-1]
184
190
 
@@ -205,7 +211,7 @@ class PytestOutputParser:
205
211
  suite_info.error_tests += 1
206
212
 
207
213
  def _process_coverage_line(self, line: str, suite_info: TestSuiteProgress) -> None:
208
- if match := self.COVERAGE_PATTERN.search(line):
214
+ if match := self._coverage_pattern.search(line):
209
215
  suite_info.coverage_percentage = float(match.group(1))
210
216
 
211
217
  def _process_current_test_line(
@@ -213,7 +219,7 @@ class PytestOutputParser:
213
219
  line: str,
214
220
  suite_info: TestSuiteProgress,
215
221
  ) -> None:
216
- if "::" in line and any(
222
+ if ":: " in line and any(
217
223
  status in line for status in ("PASSED", "FAILED", "SKIPPED", "ERROR")
218
224
  ):
219
225
  suite_info.current_test = line.split()[0] if line.split() else None
@@ -248,7 +254,7 @@ class PytestOutputParser:
248
254
  return "FAILURES" in line or "ERRORS" in line
249
255
 
250
256
  def _is_test_header(self, line: str) -> bool:
251
- return line.startswith("_") and "::" in line
257
+ return line.startswith("_") and ":: " in line
252
258
 
253
259
  def _should_add_to_traceback(self, current_test: str | None, line: str) -> bool:
254
260
  return current_test is not None and bool(line.strip())
@@ -289,7 +295,7 @@ class TestProgressStreamer:
289
295
  suite_progress = TestSuiteProgress(start_time=start_time)
290
296
 
291
297
  self.console.print(
292
- "\n[bold bright_green]🧪 RUNNING TESTS WITH STREAMING PROGRESS[/bold bright_green]",
298
+ "\n[bold bright_green]🧪 RUNNING TESTS WITH STREAMING PROGRESS[/ bold bright_green]",
293
299
  )
294
300
 
295
301
  cmd = self.build_pytest_command(options, execution_mode)
@@ -354,7 +360,7 @@ class TestProgressStreamer:
354
360
  error: Exception,
355
361
  suite_progress: TestSuiteProgress,
356
362
  ) -> dict[str, t.Any]:
357
- self.console.print(f"[red]❌ Test execution failed: {error}[/red]")
363
+ self.console.print(f"[red]❌ Test execution failed: {error}[/ red]")
358
364
  suite_progress.end_time = time.time()
359
365
  suite_progress.duration = suite_progress.end_time - (
360
366
  suite_progress.start_time or 0
@@ -375,23 +381,23 @@ class TestProgressStreamer:
375
381
  ) -> list[str]:
376
382
  cmd = ["uv", "run", "pytest"]
377
383
 
378
- cmd.extend(["-v", "--tb=short"])
384
+ cmd.extend(["- v", "--tb=short"])
379
385
 
380
386
  if hasattr(options, "coverage") and options.coverage:
381
- cmd.extend(["--cov=crackerjack", "--cov-report=term-missing"])
387
+ cmd.extend(["--cov=crackerjack", "- - cov - report=term-missing"])
382
388
 
383
389
  if execution_mode == "individual_with_progress":
384
- cmd.extend(["--no-header"])
390
+ cmd.extend(["- - no-header"])
385
391
  elif execution_mode == "selective":
386
392
  pass
387
393
  else:
388
- cmd.extend(["-q"])
394
+ cmd.extend(["- q"])
389
395
 
390
396
  if hasattr(options, "test_timeout"):
391
- cmd.extend([f"--timeout={options.test_timeout}"])
397
+ cmd.extend([f"--timeout ={options.test_timeout}"])
392
398
 
393
399
  if hasattr(options, "test_workers") and options.test_workers > 1:
394
- cmd.extend(["-n", str(options.test_workers)])
400
+ cmd.extend(["- n", str(options.test_workers)])
395
401
 
396
402
  return cmd
397
403
 
@@ -400,7 +406,7 @@ class TestProgressStreamer:
400
406
  cmd: list[str],
401
407
  suite_progress: TestSuiteProgress,
402
408
  ) -> subprocess.CompletedProcess[str]:
403
- self.console.print(f"[dim]Running: {' '.join(cmd)}[/dim]")
409
+ self.console.print(f"[dim]Running: {' '.join(cmd)}[/ dim]")
404
410
 
405
411
  process = await self._create_subprocess(cmd)
406
412
  stdout_lines: list[str] = []
@@ -517,14 +523,14 @@ class TestProgressStreamer:
517
523
  line: str,
518
524
  suite_progress: TestSuiteProgress,
519
525
  ) -> None:
520
- if "::" in line and any(
526
+ if ":: " in line and any(
521
527
  status in line for status in ("PASSED", "FAILED", "SKIPPED", "ERROR")
522
528
  ):
523
529
  parts = line.split()
524
530
  if parts:
525
531
  suite_progress.current_test = parts[0]
526
532
 
527
- if match := self.parser.TEST_COLLECTION_PATTERN.search(line):
533
+ if match := self.parser._test_collection_pattern.search(line):
528
534
  suite_progress.total_tests = int(match.group(1))
529
535
 
530
536
  if "PASSED" in line:
@@ -542,17 +548,17 @@ class TestProgressStreamer:
542
548
 
543
549
  def _print_test_line(self, line: str) -> None:
544
550
  if "PASSED" in line:
545
- self.console.print(f"[green]{line}[/green]")
551
+ self.console.print(f"[green]{line}[/ green]")
546
552
  elif "FAILED" in line:
547
- self.console.print(f"[red]{line}[/red]")
553
+ self.console.print(f"[red]{line}[/ red]")
548
554
  elif "SKIPPED" in line:
549
- self.console.print(f"[yellow]{line}[/yellow]")
555
+ self.console.print(f"[yellow]{line}[/ yellow]")
550
556
  elif "ERROR" in line:
551
- self.console.print(f"[bright_red]{line}[/bright_red]")
557
+ self.console.print(f"[bright_red]{line}[/ bright_red]")
552
558
  elif line.startswith("="):
553
- self.console.print(f"[bold cyan]{line}[/bold cyan]")
559
+ self.console.print(f"[bold cyan]{line}[/ bold cyan]")
554
560
  else:
555
- self.console.print(f"[dim]{line}[/dim]")
561
+ self.console.print(f"[dim]{line}[/ dim]")
556
562
 
557
563
  def _print_test_summary(
558
564
  self,
@@ -569,45 +575,47 @@ class TestProgressStreamer:
569
575
  def _print_summary_header(self) -> None:
570
576
  self.console.print("\n" + "=" * 80)
571
577
  self.console.print(
572
- "[bold bright_green]🧪 TEST EXECUTION SUMMARY[/bold bright_green]",
578
+ "[bold bright_green]🧪 TEST EXECUTION SUMMARY[/ bold bright_green]",
573
579
  )
574
580
  self.console.print("=" * 80)
575
581
 
576
582
  def _print_test_counts(self, suite_progress: TestSuiteProgress) -> None:
577
- self.console.print(f"[bold]Total Tests: [/bold] {suite_progress.total_tests}")
578
- self.console.print(f"[green]✅ Passed: [/green] {suite_progress.passed_tests}")
583
+ self.console.print(f"[bold]Total Tests: [/ bold] {suite_progress.total_tests}")
584
+ self.console.print(f"[green]✅ Passed: [/ green] {suite_progress.passed_tests}")
579
585
 
580
586
  if suite_progress.failed_tests > 0:
581
- self.console.print(f"[red]❌ Failed: [/red] {suite_progress.failed_tests}")
587
+ self.console.print(f"[red]❌ Failed: [/ red] {suite_progress.failed_tests}")
582
588
 
583
589
  if suite_progress.skipped_tests > 0:
584
590
  self.console.print(
585
- f"[yellow]⏭️ Skipped: [/yellow] {suite_progress.skipped_tests}",
591
+ f"[yellow]⏭️ Skipped: [/ yellow] {suite_progress.skipped_tests}",
586
592
  )
587
593
 
588
594
  if suite_progress.error_tests > 0:
589
595
  self.console.print(
590
- f"[bright_red]💥 Errors: [/bright_red] {suite_progress.error_tests}",
596
+ f"[bright_red]💥 Errors: [/ bright_red] {suite_progress.error_tests}",
591
597
  )
592
598
 
593
599
  def _print_timing_stats(self, suite_progress: TestSuiteProgress) -> None:
594
600
  if not suite_progress.duration:
595
601
  return
596
602
 
597
- self.console.print(f"[bold]⏱️ Duration: [/bold] {suite_progress.duration:.1f}s")
603
+ self.console.print(
604
+ f"[bold]⏱️ Duration: [/ bold] {suite_progress.duration: .1f}s"
605
+ )
598
606
 
599
607
  if suite_progress.total_tests > 0:
600
608
  avg_time = suite_progress.duration / suite_progress.total_tests
601
- self.console.print(f"[dim]Average per test: {avg_time:.2f}s[/dim]")
609
+ self.console.print(f"[dim]Average per test: {avg_time: .2f}s[/ dim]")
602
610
 
603
611
  self.console.print(
604
- f"[bold]📊 Success Rate: [/bold] {suite_progress.success_rate:.1f}%",
612
+ f"[bold]📊 Success Rate: [/ bold] {suite_progress.success_rate: .1f}%",
605
613
  )
606
614
 
607
615
  def _print_coverage_stats(self, suite_progress: TestSuiteProgress) -> None:
608
616
  if suite_progress.coverage_percentage is not None:
609
617
  self.console.print(
610
- f"[bold]📈 Coverage: [/bold] {suite_progress.coverage_percentage:.1f}%",
618
+ f"[bold]📈 Coverage: [/ bold] {suite_progress.coverage_percentage: .1f}%",
611
619
  )
612
620
 
613
621
  def _print_failed_test_details(self, tests: list[TestProgress]) -> None:
@@ -616,16 +624,16 @@ class TestProgressStreamer:
616
624
  return
617
625
 
618
626
  self.console.print(
619
- f"\n[bold red]Failed Tests ({len(failed_tests)}): [/bold red]",
627
+ f"\n[bold red]Failed Tests ({len(failed_tests)}): [/ bold red]",
620
628
  )
621
629
  for test in failed_tests[:5]:
622
630
  self.console.print(f" ❌ {test.test_id}")
623
631
  if test.error_message:
624
632
  error_preview = self._format_error_preview(test.error_message)
625
- self.console.print(f" [dim]{error_preview}[/dim]")
633
+ self.console.print(f" [dim]{error_preview}[/ dim]")
626
634
 
627
635
  if len(failed_tests) > 5:
628
- self.console.print(f" [dim]... and {len(failed_tests) - 5} more[/dim]")
636
+ self.console.print(f" [dim]... and {len(failed_tests) - 5} more[/ dim]")
629
637
 
630
638
  def _format_error_preview(self, error_message: str) -> str:
631
639
  return (
@@ -21,7 +21,7 @@ class PluginMetadata:
21
21
  description: str
22
22
  author: str = ""
23
23
  license: str = ""
24
- requires_python: str = ">=3.11"
24
+ requires_python: str = "> = 3.11"
25
25
  dependencies: list[str] = field(default_factory=list)
26
26
  entry_point: str = ""
27
27
  config_schema: dict[str, t.Any] = field(default_factory=dict)
@@ -44,7 +44,7 @@ class PluginManager:
44
44
 
45
45
  if total_count > 0:
46
46
  self.console.print(
47
- f"[green]✅[/green] Loaded {loaded_count} / {total_count} plugins",
47
+ f"[green]✅[/ green] Loaded {loaded_count} / {total_count} plugins",
48
48
  )
49
49
 
50
50
  activation_results = self.registry.activate_all()
@@ -54,7 +54,7 @@ class PluginManager:
54
54
 
55
55
  if activated_count > 0:
56
56
  self.console.print(
57
- f"[green]✅[/green] Activated {activated_count} plugins",
57
+ f"[green]✅[/ green] Activated {activated_count} plugins",
58
58
  )
59
59
 
60
60
  hook_plugins = self.registry.get_enabled(PluginType.HOOK)
@@ -69,7 +69,7 @@ class PluginManager:
69
69
  except Exception as e:
70
70
  self.logger.exception(f"Failed to initialize plugin system: {e}")
71
71
  self.console.print(
72
- f"[red]❌[/red] Plugin system initialization failed: {e}",
72
+ f"[red]❌[/ red] Plugin system initialization failed: {e}",
73
73
  )
74
74
  return False
75
75
 
@@ -85,7 +85,7 @@ class PluginManager:
85
85
 
86
86
  if deactivated_count > 0:
87
87
  self.console.print(
88
- f"[yellow]⏹️[/yellow] Deactivated {deactivated_count} plugins",
88
+ f"[yellow]⏹️[/ yellow] Deactivated {deactivated_count} plugins",
89
89
  )
90
90
 
91
91
  self._initialized = False
@@ -128,12 +128,12 @@ class PluginManager:
128
128
  def enable_plugin(self, plugin_name: str) -> bool:
129
129
  plugin = self.registry.get(plugin_name)
130
130
  if not plugin:
131
- self.console.print(f"[red]❌[/red] Plugin not found: {plugin_name}")
131
+ self.console.print(f"[red]❌[/ red] Plugin not found: {plugin_name}")
132
132
  return False
133
133
 
134
134
  if plugin.enabled:
135
135
  self.console.print(
136
- f"[yellow]⚠️[/yellow] Plugin already enabled: {plugin_name}",
136
+ f"[yellow]⚠️[/ yellow] Plugin already enabled: {plugin_name}",
137
137
  )
138
138
  return True
139
139
 
@@ -142,7 +142,7 @@ class PluginManager:
142
142
  success = plugin.activate()
143
143
 
144
144
  if success:
145
- self.console.print(f"[green]✅[/green] Enabled plugin: {plugin_name}")
145
+ self.console.print(f"[green]✅[/ green] Enabled plugin: {plugin_name}")
146
146
 
147
147
  if plugin.plugin_type == PluginType.HOOK:
148
148
  self.hook_registry.register_hook_plugin(plugin)
@@ -150,25 +150,25 @@ class PluginManager:
150
150
  return True
151
151
  plugin.disable()
152
152
  self.console.print(
153
- f"[red]❌[/red] Failed to activate plugin: {plugin_name}",
153
+ f"[red]❌[/ red] Failed to activate plugin: {plugin_name}",
154
154
  )
155
155
  return False
156
156
 
157
157
  except Exception as e:
158
158
  self.console.print(
159
- f"[red]❌[/red] Error enabling plugin {plugin_name}: {e}",
159
+ f"[red]❌[/ red] Error enabling plugin {plugin_name}: {e}",
160
160
  )
161
161
  return False
162
162
 
163
163
  def disable_plugin(self, plugin_name: str) -> bool:
164
164
  plugin = self.registry.get(plugin_name)
165
165
  if not plugin:
166
- self.console.print(f"[red]❌[/red] Plugin not found: {plugin_name}")
166
+ self.console.print(f"[red]❌[/ red] Plugin not found: {plugin_name}")
167
167
  return False
168
168
 
169
169
  if not plugin.enabled:
170
170
  self.console.print(
171
- f"[yellow]⚠️[/yellow] Plugin already disabled: {plugin_name}",
171
+ f"[yellow]⚠️[/ yellow] Plugin already disabled: {plugin_name}",
172
172
  )
173
173
  return True
174
174
 
@@ -180,17 +180,19 @@ class PluginManager:
180
180
  self.hook_registry.unregister_hook_plugin(plugin_name)
181
181
 
182
182
  if success:
183
- self.console.print(f"[yellow]⏹️[/yellow] Disabled plugin: {plugin_name}")
183
+ self.console.print(
184
+ f"[yellow]⏹️[/ yellow] Disabled plugin: {plugin_name}"
185
+ )
184
186
  else:
185
187
  self.console.print(
186
- f"[yellow]⚠️[/yellow] Plugin disabled with warnings: {plugin_name}",
188
+ f"[yellow]⚠️[/ yellow] Plugin disabled with warnings: {plugin_name}",
187
189
  )
188
190
 
189
191
  return True
190
192
 
191
193
  except Exception as e:
192
194
  self.console.print(
193
- f"[red]❌[/red] Error disabling plugin {plugin_name}: {e}",
195
+ f"[red]❌[/ red] Error disabling plugin {plugin_name}: {e}",
194
196
  )
195
197
  return False
196
198
 
@@ -203,16 +205,16 @@ class PluginManager:
203
205
  def configure_plugin(self, plugin_name: str, config: dict[str, t.Any]) -> bool:
204
206
  plugin = self.registry.get(plugin_name)
205
207
  if not plugin:
206
- self.console.print(f"[red]❌[/red] Plugin not found: {plugin_name}")
208
+ self.console.print(f"[red]❌[/ red] Plugin not found: {plugin_name}")
207
209
  return False
208
210
 
209
211
  try:
210
212
  plugin.configure(config)
211
- self.console.print(f"[green]✅[/green] Configured plugin: {plugin_name}")
213
+ self.console.print(f"[green]✅[/ green] Configured plugin: {plugin_name}")
212
214
  return True
213
215
  except Exception as e:
214
216
  self.console.print(
215
- f"[red]❌[/red] Error configuring plugin {plugin_name}: {e}",
217
+ f"[red]❌[/ red] Error configuring plugin {plugin_name}: {e}",
216
218
  )
217
219
  return False
218
220
 
@@ -222,7 +224,7 @@ class PluginManager:
222
224
 
223
225
  if success:
224
226
  self.console.print(
225
- f"[green]✅[/green] Installed plugin from: {plugin_file}",
227
+ f"[green]✅[/ green] Installed plugin from: {plugin_file}",
226
228
  )
227
229
 
228
230
  if self._initialized:
@@ -235,14 +237,14 @@ class PluginManager:
235
237
  self.enable_plugin(latest_plugin_name)
236
238
  else:
237
239
  self.console.print(
238
- f"[red]❌[/red] Failed to install plugin from: {plugin_file}",
240
+ f"[red]❌[/ red] Failed to install plugin from: {plugin_file}",
239
241
  )
240
242
 
241
243
  return success
242
244
 
243
245
  except Exception as e:
244
246
  self.console.print(
245
- f"[red]❌[/red] Error installing plugin {plugin_file}: {e}",
247
+ f"[red]❌[/ red] Error installing plugin {plugin_file}: {e}",
246
248
  )
247
249
  return False
248
250
 
crackerjack/py313.py CHANGED
@@ -86,7 +86,7 @@ def categorize_file(file_path: Path) -> str:
86
86
  path_str = str(file_path)
87
87
  name = file_path
88
88
  match path_str:
89
- case s if name.suffix == ".py" and "/tests/" in s:
89
+ case s if name.suffix == ".py" and "/ tests /" in s:
90
90
  return "Python Test File"
91
91
  case s if name.suffix == ".py" and "__init__.py" in name.name:
92
92
  return "Python Module Init"
@@ -167,24 +167,68 @@ def clean_python_code(code: str) -> str:
167
167
  lines = code.splitlines()
168
168
  cleaned_lines: list[str] = []
169
169
  for line in lines:
170
- match line.strip():
171
- case "":
172
- if not cleaned_lines or cleaned_lines[-1].strip():
173
- cleaned_lines.append("")
174
- case s if s.startswith(("import ", "from ")):
175
- cleaned_lines.append(line)
176
- case s if s.startswith("#"):
177
- continue
178
- case s if "#" in s and (
179
- not any(
180
- skip in s for skip in ("# noqa", "# type: ", "# pragma", "# skip")
181
- )
182
- ):
183
- code_part = line.split("#", 1)[0].rstrip()
184
- if code_part:
185
- cleaned_lines.append(code_part)
186
- case s if s.startswith(('"""', "'''")):
187
- continue
188
- case _:
189
- cleaned_lines.append(line)
170
+ processed_line = _process_line_for_cleaning(line, cleaned_lines)
171
+ if processed_line is not None:
172
+ cleaned_lines.append(processed_line)
190
173
  return "\n".join(cleaned_lines)
174
+
175
+
176
+ def _process_line_for_cleaning(line: str, cleaned_lines: list[str]) -> str | None:
177
+ """Process a single line for Python code cleaning.
178
+
179
+ Returns:
180
+ The processed line to add, or None if the line should be skipped.
181
+ """
182
+ stripped = line.strip()
183
+
184
+ if _should_handle_empty_line(stripped, cleaned_lines):
185
+ return ""
186
+
187
+ if _is_import_line(stripped):
188
+ return line
189
+
190
+ if _is_comment_to_skip(stripped):
191
+ return None
192
+
193
+ if _has_inline_comment_to_process(stripped):
194
+ return _extract_code_part(line)
195
+
196
+ if _is_docstring_line(stripped):
197
+ return None
198
+
199
+ return line
200
+
201
+
202
+ def _should_handle_empty_line(stripped: str, cleaned_lines: list[str]) -> bool:
203
+ """Check if empty line should be preserved."""
204
+ return stripped == "" and (not cleaned_lines or bool(cleaned_lines[-1].strip()))
205
+
206
+
207
+ def _is_import_line(stripped: str) -> bool:
208
+ """Check if line is an import statement."""
209
+ return stripped.startswith(("import ", "from "))
210
+
211
+
212
+ def _is_comment_to_skip(stripped: str) -> bool:
213
+ """Check if line is a comment that should be skipped."""
214
+ return stripped.startswith("#")
215
+
216
+
217
+ def _has_inline_comment_to_process(stripped: str) -> bool:
218
+ """Check if line has inline comment that should be processed."""
219
+ if "#" not in stripped:
220
+ return False
221
+
222
+ skip_markers = ("# noqa", "# type: ", "# pragma", "# skip")
223
+ return not any(skip in stripped for skip in skip_markers)
224
+
225
+
226
+ def _extract_code_part(line: str) -> str | None:
227
+ """Extract code part from line with inline comment."""
228
+ code_part = line.split("#", 1)[0].rstrip()
229
+ return code_part or None
230
+
231
+
232
+ def _is_docstring_line(stripped: str) -> bool:
233
+ """Check if line starts a docstring."""
234
+ return stripped.startswith(('"""', "'''"))