crackerjack 0.31.9__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 +282 -95
  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 +355 -204
  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 +52 -62
  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 +51 -76
  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 +78 -44
  119. crackerjack/services/health_metrics.py +31 -27
  120. crackerjack/services/initialization.py +281 -433
  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.9.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.9.dist-info/RECORD +0 -149
  153. {crackerjack-0.31.9.dist-info → crackerjack-0.31.12.dist-info}/WHEEL +0 -0
  154. {crackerjack-0.31.9.dist-info → crackerjack-0.31.12.dist-info}/entry_points.txt +0 -0
  155. {crackerjack-0.31.9.dist-info → crackerjack-0.31.12.dist-info}/licenses/LICENSE +0 -0
@@ -25,12 +25,21 @@ class AsyncHookManager:
25
25
  quiet=True,
26
26
  )
27
27
  self.config_loader = HookConfigLoader()
28
+ self._config_path: Path | None = None
29
+
30
+ def set_config_path(self, config_path: Path) -> None:
31
+ """Set the path to the pre-commit configuration file."""
32
+ self._config_path = config_path
28
33
 
29
34
  async def run_fast_hooks_async(self) -> list[HookResult]:
30
35
  strategy = self.config_loader.load_strategy("fast")
31
36
 
32
37
  strategy.parallel = False
33
38
 
39
+ if self._config_path:
40
+ for hook in strategy.hooks:
41
+ hook.config_path = self._config_path
42
+
34
43
  execution_result = await self.async_executor.execute_strategy(strategy)
35
44
  return execution_result.results
36
45
 
@@ -40,6 +49,10 @@ class AsyncHookManager:
40
49
  strategy.parallel = True
41
50
  strategy.max_workers = 3
42
51
 
52
+ if self._config_path:
53
+ for hook in strategy.hooks:
54
+ hook.config_path = self._config_path
55
+
43
56
  execution_result = await self.async_executor.execute_strategy(strategy)
44
57
  return execution_result.results
45
58
 
@@ -52,6 +65,8 @@ class AsyncHookManager:
52
65
  async def install_hooks_async(self) -> bool:
53
66
  try:
54
67
  process = await asyncio.create_subprocess_exec(
68
+ "uv",
69
+ "run",
55
70
  "pre-commit",
56
71
  "install",
57
72
  cwd=self.pkg_path,
@@ -62,19 +77,19 @@ class AsyncHookManager:
62
77
  _, stderr = await asyncio.wait_for(process.communicate(), timeout=30)
63
78
 
64
79
  if process.returncode == 0:
65
- self.console.print("[green]✅[/green] Pre-commit hooks installed")
80
+ self.console.print("[green]✅[/ green] Pre-commit hooks installed")
66
81
  return True
67
82
  error_msg = stderr.decode() if stderr else "Unknown error"
68
83
  self.console.print(
69
- f"[red]❌[/red] Failed to install hooks: {error_msg}",
84
+ f"[red]❌[/ red] Failed to install hooks: {error_msg}",
70
85
  )
71
86
  return False
72
87
 
73
88
  except TimeoutError:
74
- self.console.print("[red]❌[/red] Hook installation timed out")
89
+ self.console.print("[red]❌[/ red] Hook installation timed out")
75
90
  return False
76
91
  except Exception as e:
77
- self.console.print(f"[red]❌[/red] Error installing hooks: {e}")
92
+ self.console.print(f"[red]❌[/ red] Error installing hooks: {e}")
78
93
  return False
79
94
 
80
95
  def install_hooks(self) -> bool:
@@ -83,6 +98,8 @@ class AsyncHookManager:
83
98
  async def update_hooks_async(self) -> bool:
84
99
  try:
85
100
  process = await asyncio.create_subprocess_exec(
101
+ "uv",
102
+ "run",
86
103
  "pre-commit",
87
104
  "autoupdate",
88
105
  cwd=self.pkg_path,
@@ -93,17 +110,17 @@ class AsyncHookManager:
93
110
  _, stderr = await asyncio.wait_for(process.communicate(), timeout=60)
94
111
 
95
112
  if process.returncode == 0:
96
- self.console.print("[green]✅[/green] Pre-commit hooks updated")
113
+ self.console.print("[green]✅[/ green] Pre-commit hooks updated")
97
114
  return True
98
115
  error_msg = stderr.decode() if stderr else "Unknown error"
99
- self.console.print(f"[red]❌[/red] Failed to update hooks: {error_msg}")
116
+ self.console.print(f"[red]❌[/ red] Failed to update hooks: {error_msg}")
100
117
  return False
101
118
 
102
119
  except TimeoutError:
103
- self.console.print("[red]❌[/red] Hook update timed out")
120
+ self.console.print("[red]❌[/ red] Hook update timed out")
104
121
  return False
105
122
  except Exception as e:
106
- self.console.print(f"[red]❌[/red] Error updating hooks: {e}")
123
+ self.console.print(f"[red]❌[/ red] Error updating hooks: {e}")
107
124
  return False
108
125
 
109
126
  def update_hooks(self) -> bool:
@@ -52,7 +52,7 @@ class HookManagerImpl:
52
52
  def validate_hooks_config(self) -> bool:
53
53
  try:
54
54
  result = subprocess.run(
55
- ["pre-commit", "validate-config"],
55
+ ["uv", "run", "pre-commit", "validate-config"],
56
56
  cwd=self.pkg_path,
57
57
  capture_output=True,
58
58
  text=True,
@@ -72,7 +72,7 @@ class HookManagerImpl:
72
72
  def install_hooks(self) -> bool:
73
73
  try:
74
74
  result = subprocess.run(
75
- ["pre-commit", "install"],
75
+ ["uv", "run", "pre-commit", "install"],
76
76
  check=False,
77
77
  cwd=self.pkg_path,
78
78
  capture_output=True,
@@ -80,20 +80,20 @@ class HookManagerImpl:
80
80
  timeout=30,
81
81
  )
82
82
  if result.returncode == 0:
83
- self.console.print("[green]✅[/green] Pre-commit hooks installed")
83
+ self.console.print("[green]✅[/ green] Pre-commit hooks installed")
84
84
  return True
85
85
  self.console.print(
86
- f"[red]❌[/red] Failed to install hooks: {result.stderr}",
86
+ f"[red]❌[/ red] Failed to install hooks: {result.stderr}",
87
87
  )
88
88
  return False
89
89
  except Exception as e:
90
- self.console.print(f"[red]❌[/red] Error installing hooks: {e}")
90
+ self.console.print(f"[red]❌[/ red] Error installing hooks: {e}")
91
91
  return False
92
92
 
93
93
  def update_hooks(self) -> bool:
94
94
  try:
95
95
  result = subprocess.run(
96
- ["pre-commit", "autoupdate"],
96
+ ["uv", "run", "pre-commit", "autoupdate"],
97
97
  check=False,
98
98
  cwd=self.pkg_path,
99
99
  capture_output=True,
@@ -101,14 +101,14 @@ class HookManagerImpl:
101
101
  timeout=60,
102
102
  )
103
103
  if result.returncode == 0:
104
- self.console.print("[green]✅[/green] Pre-commit hooks updated")
104
+ self.console.print("[green]✅[/ green] Pre-commit hooks updated")
105
105
  return True
106
106
  self.console.print(
107
- f"[red]❌[/red] Failed to update hooks: {result.stderr}",
107
+ f"[red]❌[/ red] Failed to update hooks: {result.stderr}",
108
108
  )
109
109
  return False
110
110
  except Exception as e:
111
- self.console.print(f"[red]❌[/red] Error updating hooks: {e}")
111
+ self.console.print(f"[red]❌[/ red] Error updating hooks: {e}")
112
112
  return False
113
113
 
114
114
  def get_hook_summary(self, results: list[HookResult]) -> dict[str, t.Any]:
@@ -52,32 +52,29 @@ class PublishManagerImpl:
52
52
  data = loads(content)
53
53
  return data.get("project", {}).get("version")
54
54
  except Exception as e:
55
- self.console.print(f"[yellow]⚠️[/yellow] Error reading version: {e}")
55
+ self.console.print(f"[yellow]⚠️[/ yellow] Error reading version: {e}")
56
56
  return None
57
57
 
58
58
  def _update_version_in_file(self, new_version: str) -> bool:
59
59
  pyproject_path = self.pkg_path / "pyproject.toml"
60
60
  try:
61
61
  content = self.filesystem.read_file(pyproject_path)
62
- import re
62
+ from crackerjack.services.regex_patterns import update_pyproject_version
63
63
 
64
- # More specific pattern to only match project version, not tool versions
65
- pattern = r'^(version\s*=\s*["\'])([^"\']+)(["\'])$'
66
- replacement = f"\\g<1>{new_version}\\g<3>"
67
- new_content = re.sub(pattern, replacement, content, flags=re.MULTILINE)
64
+ new_content = update_pyproject_version(content, new_version)
68
65
  if content != new_content:
69
66
  if not self.dry_run:
70
67
  self.filesystem.write_file(pyproject_path, new_content)
71
68
  self.console.print(
72
- f"[green]✅[/green] Updated version to {new_version}",
69
+ f"[green]✅[/ green] Updated version to {new_version}",
73
70
  )
74
71
  return True
75
72
  self.console.print(
76
- "[yellow]⚠️[/yellow] Version pattern not found in pyproject.toml",
73
+ "[yellow]⚠️[/ yellow] Version pattern not found in pyproject.toml",
77
74
  )
78
75
  return False
79
76
  except Exception as e:
80
- self.console.print(f"[red]❌[/red] Error updating version: {e}")
77
+ self.console.print(f"[red]❌[/ red] Error updating version: {e}")
81
78
  return False
82
79
 
83
80
  def _calculate_next_version(self, current: str, bump_type: str) -> str:
@@ -96,18 +93,17 @@ class PublishManagerImpl:
96
93
  msg = f"Invalid bump type: {bump_type}"
97
94
  raise ValueError(msg)
98
95
  except Exception as e:
99
- self.console.print(f"[red]❌[/red] Error calculating version: {e}")
96
+ self.console.print(f"[red]❌[/ red] Error calculating version: {e}")
100
97
  raise
101
98
 
102
99
  def bump_version(self, version_type: str) -> str:
103
100
  current_version = self._get_current_version()
104
101
  if not current_version:
105
- self.console.print("[red]❌[/red] Could not determine current version")
102
+ self.console.print("[red]❌[/ red] Could not determine current version")
106
103
  msg = "Cannot determine current version"
107
104
  raise ValueError(msg)
108
- self.console.print(f"[cyan]📦[/cyan] Current version: {current_version}")
105
+ self.console.print(f"[cyan]📦[/ cyan] Current version: {current_version}")
109
106
 
110
- # Handle interactive version selection
111
107
  if version_type == "interactive":
112
108
  version_type = self._prompt_for_version_type()
113
109
 
@@ -115,11 +111,11 @@ class PublishManagerImpl:
115
111
  new_version = self._calculate_next_version(current_version, version_type)
116
112
  if self.dry_run:
117
113
  self.console.print(
118
- f"[yellow]🔍[/yellow] Would bump {version_type} version: {current_version} → {new_version}",
114
+ f"[yellow]🔍[/ yellow] Would bump {version_type} version: {current_version} → {new_version}",
119
115
  )
120
116
  elif self._update_version_in_file(new_version):
121
117
  self.console.print(
122
- f"[green]🚀[/green] Bumped {version_type} version: {current_version} → {new_version}",
118
+ f"[green]🚀[/ green] Bumped {version_type} version: {current_version} → {new_version}",
123
119
  )
124
120
  else:
125
121
  msg = "Failed to update version in file"
@@ -127,22 +123,21 @@ class PublishManagerImpl:
127
123
 
128
124
  return new_version
129
125
  except Exception as e:
130
- self.console.print(f"[red]❌[/red] Version bump failed: {e}")
126
+ self.console.print(f"[red]❌[/ red] Version bump failed: {e}")
131
127
  raise
132
128
 
133
129
  def _prompt_for_version_type(self) -> str:
134
- """Prompt user to select version type interactively."""
135
130
  try:
136
131
  from rich.prompt import Prompt
137
132
 
138
133
  return Prompt.ask(
139
- "[cyan]📦[/cyan] Select version bump type",
134
+ "[cyan]📦[/ cyan] Select version bump type",
140
135
  choices=["patch", "minor", "major"],
141
136
  default="patch",
142
137
  )
143
138
  except ImportError:
144
139
  self.console.print(
145
- "[yellow]⚠️[/yellow] Rich prompt not available, defaulting to patch"
140
+ "[yellow]⚠️[/ yellow] Rich prompt not available, defaulting to patch"
146
141
  )
147
142
  return "patch"
148
143
 
@@ -172,24 +167,29 @@ class PublishManagerImpl:
172
167
 
173
168
  if self.security.validate_token_format(token, "pypi"):
174
169
  masked_token = self.security.mask_tokens(token)
175
- self.console.print(f"[dim]Token format: {masked_token}[/dim]", style="dim")
170
+ self.console.print(f"[dim]Token format: {masked_token}[/ dim]", style="dim")
176
171
  return "Environment variable (UV_PUBLISH_TOKEN)"
177
172
  self.console.print(
178
- "[yellow]⚠️[/yellow] UV_PUBLISH_TOKEN format appears invalid",
173
+ "[yellow]⚠️[/ yellow] UV_PUBLISH_TOKEN format appears invalid",
179
174
  )
180
175
  return None
181
176
 
182
177
  def _check_keyring_auth(self) -> str | None:
183
178
  try:
184
179
  result = self._run_command(
185
- ["keyring", "get", "https://upload.pypi.org/legacy/", "__token__"],
180
+ [
181
+ "keyring",
182
+ "get",
183
+ "https://upload.pypi.org/legacy/",
184
+ "__token__",
185
+ ],
186
186
  )
187
187
  if result.returncode == 0 and result.stdout.strip():
188
188
  keyring_token = result.stdout.strip()
189
189
  if self.security.validate_token_format(keyring_token, "pypi"):
190
190
  return "Keyring storage"
191
191
  self.console.print(
192
- "[yellow]⚠️[/yellow] Keyring token format appears invalid",
192
+ "[yellow]⚠️[/ yellow] Keyring token format appears invalid",
193
193
  )
194
194
  except (subprocess.SubprocessError, OSError, FileNotFoundError):
195
195
  pass
@@ -197,16 +197,16 @@ class PublishManagerImpl:
197
197
 
198
198
  def _report_auth_status(self, auth_methods: list[str]) -> bool:
199
199
  if auth_methods:
200
- self.console.print("[green]✅[/green] PyPI authentication available: ")
200
+ self.console.print("[green]✅[/ green] PyPI authentication available: ")
201
201
  for method in auth_methods:
202
- self.console.print(f" - {method}")
202
+ self.console.print(f"-{method}")
203
203
  return True
204
204
  self._display_auth_setup_instructions()
205
205
  return False
206
206
 
207
207
  def _display_auth_setup_instructions(self) -> None:
208
- self.console.print("[red]❌[/red] No valid PyPI authentication found")
209
- self.console.print("\n[yellow]💡[/yellow] Setup options: ")
208
+ self.console.print("[red]❌[/ red] No valid PyPI authentication found")
209
+ self.console.print("\n[yellow]💡[/ yellow] Setup options: ")
210
210
  self.console.print(
211
211
  " 1. Set environment variable: export UV_PUBLISH_TOKEN=<your-pypi-token>",
212
212
  )
@@ -219,22 +219,21 @@ class PublishManagerImpl:
219
219
 
220
220
  def build_package(self) -> bool:
221
221
  try:
222
- self.console.print("[yellow]🔨[/yellow] Building package...")
222
+ self.console.print("[yellow]🔨[/ yellow] Building package...")
223
223
 
224
224
  if self.dry_run:
225
225
  return self._handle_dry_run_build()
226
226
 
227
227
  return self._execute_build()
228
228
  except Exception as e:
229
- self.console.print(f"[red]❌[/red] Build error: {e}")
229
+ self.console.print(f"[red]❌[/ red] Build error: {e}")
230
230
  return False
231
231
 
232
232
  def _handle_dry_run_build(self) -> bool:
233
- self.console.print("[yellow]🔍[/yellow] Would build package")
233
+ self.console.print("[yellow]🔍[/ yellow] Would build package")
234
234
  return True
235
235
 
236
236
  def _clean_dist_directory(self) -> None:
237
- """Clean dist directory to ensure only current version artifacts are uploaded."""
238
237
  dist_dir = self.pkg_path / "dist"
239
238
  if not dist_dir.exists():
240
239
  return
@@ -242,27 +241,26 @@ class PublishManagerImpl:
242
241
  try:
243
242
  import shutil
244
243
 
245
- # Remove entire dist directory and recreate it
246
244
  shutil.rmtree(dist_dir)
247
245
  dist_dir.mkdir(exist_ok=True)
248
- self.console.print("[cyan]🧹[/cyan] Cleaned dist directory for fresh build")
246
+ self.console.print(
247
+ "[cyan]🧹[/ cyan] Cleaned dist directory for fresh build"
248
+ )
249
249
  except Exception as e:
250
250
  self.console.print(
251
- f"[yellow]⚠️[/yellow] Warning: Could not clean dist directory: {e}"
251
+ f"[yellow]⚠️[/ yellow] Warning: Could not clean dist directory: {e}"
252
252
  )
253
- # Continue with build anyway - uv publish will fail with clear error
254
253
 
255
254
  def _execute_build(self) -> bool:
256
- # Clean dist directory before building to avoid uploading multiple versions
257
255
  self._clean_dist_directory()
258
256
 
259
257
  result = self._run_command(["uv", "build"])
260
258
 
261
259
  if result.returncode != 0:
262
- self.console.print(f"[red]❌[/red] Build failed: {result.stderr}")
260
+ self.console.print(f"[red]❌[/ red] Build failed: {result.stderr}")
263
261
  return False
264
262
 
265
- self.console.print("[green]✅[/green] Package built successfully")
263
+ self.console.print("[green]✅[/ green] Package built successfully")
266
264
  self._display_build_artifacts()
267
265
  return True
268
266
 
@@ -272,11 +270,11 @@ class PublishManagerImpl:
272
270
  return
273
271
 
274
272
  artifacts = list(dist_dir.glob("*"))
275
- self.console.print(f"[cyan]📦[/cyan] Build artifacts ({len(artifacts)}): ")
273
+ self.console.print(f"[cyan]📦[/ cyan] Build artifacts ({len(artifacts)}): ")
276
274
 
277
275
  for artifact in artifacts[-5:]:
278
276
  size_str = self._format_file_size(artifact.stat().st_size)
279
- self.console.print(f" - {artifact.name} ({size_str})")
277
+ self.console.print(f"-{artifact.name} ({size_str})")
280
278
 
281
279
  def _format_file_size(self, size: int) -> str:
282
280
  if size < 1024 * 1024:
@@ -288,10 +286,10 @@ class PublishManagerImpl:
288
286
  return False
289
287
 
290
288
  try:
291
- self.console.print("[yellow]🚀[/yellow] Publishing to PyPI...")
289
+ self.console.print("[yellow]🚀[/ yellow] Publishing to PyPI...")
292
290
  return self._perform_publish_workflow()
293
291
  except Exception as e:
294
- self.console.print(f"[red]❌[/red] Publish error: {e}")
292
+ self.console.print(f"[red]❌[/ red] Publish error: {e}")
295
293
  return False
296
294
 
297
295
  def _validate_prerequisites(self) -> bool:
@@ -307,7 +305,7 @@ class PublishManagerImpl:
307
305
  return self._execute_publish()
308
306
 
309
307
  def _handle_dry_run_publish(self) -> bool:
310
- self.console.print("[yellow]🔍[/yellow] Would publish package to PyPI")
308
+ self.console.print("[yellow]🔍[/ yellow] Would publish package to PyPI")
311
309
  return True
312
310
 
313
311
  def _execute_publish(self) -> bool:
@@ -321,10 +319,10 @@ class PublishManagerImpl:
321
319
  return True
322
320
 
323
321
  def _handle_publish_failure(self, error_msg: str) -> None:
324
- self.console.print(f"[red]❌[/red] Publish failed: {error_msg}")
322
+ self.console.print(f"[red]❌[/ red] Publish failed: {error_msg}")
325
323
 
326
324
  def _handle_publish_success(self) -> None:
327
- self.console.print("[green]🎉[/green] Package published successfully!")
325
+ self.console.print("[green]🎉[/ green] Package published successfully !")
328
326
  self._display_package_url()
329
327
 
330
328
  def _display_package_url(self) -> None:
@@ -333,7 +331,7 @@ class PublishManagerImpl:
333
331
 
334
332
  if package_name and current_version:
335
333
  url = f"https://pypi.org/project/{package_name}/{current_version}/"
336
- self.console.print(f"[cyan]🔗[/cyan] Package URL: {url}")
334
+ self.console.print(f"[cyan]🔗[/ cyan] Package URL: {url}")
337
335
 
338
336
  def _get_package_name(self) -> str | None:
339
337
  pyproject_path = self.pkg_path / "pyproject.toml"
@@ -350,11 +348,11 @@ class PublishManagerImpl:
350
348
  def cleanup_old_releases(self, keep_releases: int = 10) -> bool:
351
349
  try:
352
350
  self.console.print(
353
- f"[yellow]🧹[/yellow] Cleaning up old releases (keeping {keep_releases})...",
351
+ f"[yellow]🧹[/ yellow] Cleaning up old releases (keeping {keep_releases})...",
354
352
  )
355
353
  if self.dry_run:
356
354
  self.console.print(
357
- "[yellow]🔍[/yellow] Would clean up old PyPI releases",
355
+ "[yellow]🔍[/ yellow] Would clean up old PyPI releases",
358
356
  )
359
357
  return True
360
358
  pyproject_path = self.pkg_path / "pyproject.toml"
@@ -365,48 +363,48 @@ class PublishManagerImpl:
365
363
  package_name = data.get("project", {}).get("name", "")
366
364
  if not package_name:
367
365
  self.console.print(
368
- "[yellow]⚠️[/yellow] Could not determine package name",
366
+ "[yellow]⚠️[/ yellow] Could not determine package name",
369
367
  )
370
368
  return False
371
369
  self.console.print(
372
- f"[cyan]📦[/cyan] Would analyze releases for {package_name}",
370
+ f"[cyan]📦[/ cyan] Would analyze releases for {package_name}",
373
371
  )
374
372
  self.console.print(
375
- f"[cyan]🔧[/cyan] Would keep {keep_releases} most recent releases",
373
+ f"[cyan]🔧[/ cyan] Would keep {keep_releases} most recent releases",
376
374
  )
377
375
 
378
376
  return True
379
377
  except Exception as e:
380
- self.console.print(f"[red]❌[/red] Cleanup error: {e}")
378
+ self.console.print(f"[red]❌[/ red] Cleanup error: {e}")
381
379
  return False
382
380
 
383
381
  def create_git_tag(self, version: str) -> bool:
384
382
  try:
385
383
  if self.dry_run:
386
384
  self.console.print(
387
- f"[yellow]🔍[/yellow] Would create git tag: v{version}",
385
+ f"[yellow]🔍[/ yellow] Would create git tag: v{version}",
388
386
  )
389
387
  return True
390
388
  result = self._run_command(["git", "tag", f"v{version}"])
391
389
  if result.returncode == 0:
392
- self.console.print(f"[green]🏷️[/green] Created git tag: v{version}")
390
+ self.console.print(f"[green]🏷️[/ green] Created git tag: v{version}")
393
391
  push_result = self._run_command(
394
392
  ["git", "push", "origin", f"v{version}"],
395
393
  )
396
394
  if push_result.returncode == 0:
397
- self.console.print("[green]📤[/green] Pushed tag to remote")
395
+ self.console.print("[green]📤[/ green] Pushed tag to remote")
398
396
  else:
399
397
  self.console.print(
400
- f"[yellow]⚠️[/yellow] Tag created but push failed: {push_result.stderr}",
398
+ f"[yellow]⚠️[/ yellow] Tag created but push failed: {push_result.stderr}",
401
399
  )
402
400
 
403
401
  return True
404
402
  self.console.print(
405
- f"[red]❌[/red] Failed to create tag: {result.stderr}",
403
+ f"[red]❌[/ red] Failed to create tag: {result.stderr}",
406
404
  )
407
405
  return False
408
406
  except Exception as e:
409
- self.console.print(f"[red]❌[/red] Tag creation error: {e}")
407
+ self.console.print(f"[red]❌[/ red] Tag creation error: {e}")
410
408
  return False
411
409
 
412
410
  def get_package_info(self) -> dict[str, t.Any]:
@@ -429,5 +427,5 @@ class PublishManagerImpl:
429
427
  "python_requires": project.get("requires-python", ""),
430
428
  }
431
429
  except Exception as e:
432
- self.console.print(f"[yellow]⚠️[/yellow] Error reading package info: {e}")
430
+ self.console.print(f"[yellow]⚠️[/ yellow] Error reading package info: {e}")
433
431
  return {}
@@ -1,22 +1,13 @@
1
- """Test command building and configuration.
2
-
3
- This module handles pytest command construction with various options and configurations.
4
- Split from test_manager.py for better separation of concerns.
5
- """
6
-
7
1
  from pathlib import Path
8
2
 
9
3
  from crackerjack.models.protocols import OptionsProtocol
10
4
 
11
5
 
12
6
  class TestCommandBuilder:
13
- """Builds pytest commands with appropriate options and configurations."""
14
-
15
7
  def __init__(self, pkg_path: Path) -> None:
16
8
  self.pkg_path = pkg_path
17
9
 
18
10
  def build_command(self, options: OptionsProtocol) -> list[str]:
19
- """Build complete pytest command with all options."""
20
11
  cmd = ["python", "-m", "pytest"]
21
12
 
22
13
  self._add_coverage_options(cmd, options)
@@ -29,16 +20,13 @@ class TestCommandBuilder:
29
20
  return cmd
30
21
 
31
22
  def get_optimal_workers(self, options: OptionsProtocol) -> int:
32
- """Calculate optimal number of pytest workers based on system and configuration."""
33
23
  if hasattr(options, "test_workers") and options.test_workers:
34
24
  return options.test_workers
35
25
 
36
- # Auto-detect based on CPU count
37
26
  import multiprocessing
38
27
 
39
28
  cpu_count = multiprocessing.cpu_count()
40
29
 
41
- # Conservative worker count to avoid overwhelming the system
42
30
  if cpu_count <= 2:
43
31
  return 1
44
32
  elif cpu_count <= 4:
@@ -48,35 +36,29 @@ class TestCommandBuilder:
48
36
  return 4
49
37
 
50
38
  def get_test_timeout(self, options: OptionsProtocol) -> int:
51
- """Get test timeout based on options or default."""
52
39
  if hasattr(options, "test_timeout") and options.test_timeout:
53
40
  return options.test_timeout
54
41
 
55
- # Default timeout based on test configuration
56
42
  if hasattr(options, "benchmark") and options.benchmark:
57
- return 900 # 15 minutes for benchmarks
58
- return 300 # 5 minutes for regular tests
43
+ return 900
44
+ return 300
59
45
 
60
46
  def _add_coverage_options(self, cmd: list[str], options: OptionsProtocol) -> None:
61
- """Add coverage-related options to command."""
62
- # Always include coverage for comprehensive testing
63
47
  cmd.extend(
64
48
  [
65
49
  "--cov=crackerjack",
66
50
  "--cov-report=term-missing",
67
51
  "--cov-report=html",
68
- "--cov-fail-under=0", # Don't fail on low coverage, let ratchet handle it
52
+ "--cov-fail-under=0",
69
53
  ]
70
54
  )
71
55
 
72
56
  def _add_worker_options(self, cmd: list[str], options: OptionsProtocol) -> None:
73
- """Add parallel execution options to command."""
74
57
  workers = self.get_optimal_workers(options)
75
58
  if workers > 1:
76
59
  cmd.extend(["-n", str(workers)])
77
60
 
78
61
  def _add_benchmark_options(self, cmd: list[str], options: OptionsProtocol) -> None:
79
- """Add benchmark-specific options to command."""
80
62
  if hasattr(options, "benchmark") and options.benchmark:
81
63
  cmd.extend(
82
64
  [
@@ -87,27 +69,21 @@ class TestCommandBuilder:
87
69
  )
88
70
 
89
71
  def _add_timeout_options(self, cmd: list[str], options: OptionsProtocol) -> None:
90
- """Add timeout options to command."""
91
72
  timeout = self.get_test_timeout(options)
92
73
  cmd.extend(["--timeout", str(timeout)])
93
74
 
94
75
  def _add_verbosity_options(self, cmd: list[str], options: OptionsProtocol) -> None:
95
- """Add verbosity and output formatting options."""
96
- # Always use verbose output for better progress tracking
97
76
  cmd.append("-v")
98
77
 
99
- # Add useful output options
100
78
  cmd.extend(
101
79
  [
102
- "--tb=short", # Shorter traceback format
103
- "--strict-markers", # Ensure all markers are defined
104
- "--strict-config", # Ensure configuration is valid
80
+ "--tb=short",
81
+ "--strict-markers",
82
+ "--strict-config",
105
83
  ]
106
84
  )
107
85
 
108
86
  def _add_test_path(self, cmd: list[str]) -> None:
109
- """Add test path to command."""
110
- # Add tests directory if it exists, otherwise current directory
111
87
  test_paths = ["tests", "test"]
112
88
 
113
89
  for test_path in test_paths:
@@ -116,14 +92,11 @@ class TestCommandBuilder:
116
92
  cmd.append(str(full_path))
117
93
  return
118
94
 
119
- # Fallback to current directory
120
95
  cmd.append(str(self.pkg_path))
121
96
 
122
97
  def build_specific_test_command(self, test_pattern: str) -> list[str]:
123
- """Build command for running specific tests matching a pattern."""
124
98
  cmd = ["python", "-m", "pytest", "-v"]
125
99
 
126
- # Add basic coverage
127
100
  cmd.extend(
128
101
  [
129
102
  "--cov=crackerjack",
@@ -131,16 +104,13 @@ class TestCommandBuilder:
131
104
  ]
132
105
  )
133
106
 
134
- # Add the test pattern
135
107
  cmd.extend(["-k", test_pattern])
136
108
 
137
- # Add test path
138
109
  self._add_test_path(cmd)
139
110
 
140
111
  return cmd
141
112
 
142
113
  def build_validation_command(self) -> list[str]:
143
- """Build command for test environment validation."""
144
114
  return [
145
115
  "python",
146
116
  "-m",