crackerjack 0.29.0__py3-none-any.whl → 0.31.4__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 (158) hide show
  1. crackerjack/CLAUDE.md +1005 -0
  2. crackerjack/RULES.md +380 -0
  3. crackerjack/__init__.py +42 -13
  4. crackerjack/__main__.py +225 -253
  5. crackerjack/agents/__init__.py +41 -0
  6. crackerjack/agents/architect_agent.py +281 -0
  7. crackerjack/agents/base.py +169 -0
  8. crackerjack/agents/coordinator.py +512 -0
  9. crackerjack/agents/documentation_agent.py +498 -0
  10. crackerjack/agents/dry_agent.py +388 -0
  11. crackerjack/agents/formatting_agent.py +245 -0
  12. crackerjack/agents/import_optimization_agent.py +281 -0
  13. crackerjack/agents/performance_agent.py +669 -0
  14. crackerjack/agents/proactive_agent.py +104 -0
  15. crackerjack/agents/refactoring_agent.py +788 -0
  16. crackerjack/agents/security_agent.py +529 -0
  17. crackerjack/agents/test_creation_agent.py +652 -0
  18. crackerjack/agents/test_specialist_agent.py +486 -0
  19. crackerjack/agents/tracker.py +212 -0
  20. crackerjack/api.py +560 -0
  21. crackerjack/cli/__init__.py +24 -0
  22. crackerjack/cli/facade.py +104 -0
  23. crackerjack/cli/handlers.py +267 -0
  24. crackerjack/cli/interactive.py +471 -0
  25. crackerjack/cli/options.py +401 -0
  26. crackerjack/cli/utils.py +18 -0
  27. crackerjack/code_cleaner.py +670 -0
  28. crackerjack/config/__init__.py +19 -0
  29. crackerjack/config/hooks.py +218 -0
  30. crackerjack/core/__init__.py +0 -0
  31. crackerjack/core/async_workflow_orchestrator.py +406 -0
  32. crackerjack/core/autofix_coordinator.py +200 -0
  33. crackerjack/core/container.py +104 -0
  34. crackerjack/core/enhanced_container.py +542 -0
  35. crackerjack/core/performance.py +243 -0
  36. crackerjack/core/phase_coordinator.py +561 -0
  37. crackerjack/core/proactive_workflow.py +316 -0
  38. crackerjack/core/session_coordinator.py +289 -0
  39. crackerjack/core/workflow_orchestrator.py +640 -0
  40. crackerjack/dynamic_config.py +577 -0
  41. crackerjack/errors.py +263 -41
  42. crackerjack/executors/__init__.py +11 -0
  43. crackerjack/executors/async_hook_executor.py +431 -0
  44. crackerjack/executors/cached_hook_executor.py +242 -0
  45. crackerjack/executors/hook_executor.py +345 -0
  46. crackerjack/executors/individual_hook_executor.py +669 -0
  47. crackerjack/intelligence/__init__.py +44 -0
  48. crackerjack/intelligence/adaptive_learning.py +751 -0
  49. crackerjack/intelligence/agent_orchestrator.py +551 -0
  50. crackerjack/intelligence/agent_registry.py +414 -0
  51. crackerjack/intelligence/agent_selector.py +502 -0
  52. crackerjack/intelligence/integration.py +290 -0
  53. crackerjack/interactive.py +576 -315
  54. crackerjack/managers/__init__.py +11 -0
  55. crackerjack/managers/async_hook_manager.py +135 -0
  56. crackerjack/managers/hook_manager.py +137 -0
  57. crackerjack/managers/publish_manager.py +411 -0
  58. crackerjack/managers/test_command_builder.py +151 -0
  59. crackerjack/managers/test_executor.py +435 -0
  60. crackerjack/managers/test_manager.py +258 -0
  61. crackerjack/managers/test_manager_backup.py +1124 -0
  62. crackerjack/managers/test_progress.py +144 -0
  63. crackerjack/mcp/__init__.py +0 -0
  64. crackerjack/mcp/cache.py +336 -0
  65. crackerjack/mcp/client_runner.py +104 -0
  66. crackerjack/mcp/context.py +615 -0
  67. crackerjack/mcp/dashboard.py +636 -0
  68. crackerjack/mcp/enhanced_progress_monitor.py +479 -0
  69. crackerjack/mcp/file_monitor.py +336 -0
  70. crackerjack/mcp/progress_components.py +569 -0
  71. crackerjack/mcp/progress_monitor.py +949 -0
  72. crackerjack/mcp/rate_limiter.py +332 -0
  73. crackerjack/mcp/server.py +22 -0
  74. crackerjack/mcp/server_core.py +244 -0
  75. crackerjack/mcp/service_watchdog.py +501 -0
  76. crackerjack/mcp/state.py +395 -0
  77. crackerjack/mcp/task_manager.py +257 -0
  78. crackerjack/mcp/tools/__init__.py +17 -0
  79. crackerjack/mcp/tools/core_tools.py +249 -0
  80. crackerjack/mcp/tools/error_analyzer.py +308 -0
  81. crackerjack/mcp/tools/execution_tools.py +370 -0
  82. crackerjack/mcp/tools/execution_tools_backup.py +1097 -0
  83. crackerjack/mcp/tools/intelligence_tool_registry.py +80 -0
  84. crackerjack/mcp/tools/intelligence_tools.py +314 -0
  85. crackerjack/mcp/tools/monitoring_tools.py +502 -0
  86. crackerjack/mcp/tools/proactive_tools.py +384 -0
  87. crackerjack/mcp/tools/progress_tools.py +141 -0
  88. crackerjack/mcp/tools/utility_tools.py +341 -0
  89. crackerjack/mcp/tools/workflow_executor.py +360 -0
  90. crackerjack/mcp/websocket/__init__.py +14 -0
  91. crackerjack/mcp/websocket/app.py +39 -0
  92. crackerjack/mcp/websocket/endpoints.py +559 -0
  93. crackerjack/mcp/websocket/jobs.py +253 -0
  94. crackerjack/mcp/websocket/server.py +116 -0
  95. crackerjack/mcp/websocket/websocket_handler.py +78 -0
  96. crackerjack/mcp/websocket_server.py +10 -0
  97. crackerjack/models/__init__.py +31 -0
  98. crackerjack/models/config.py +93 -0
  99. crackerjack/models/config_adapter.py +230 -0
  100. crackerjack/models/protocols.py +118 -0
  101. crackerjack/models/task.py +154 -0
  102. crackerjack/monitoring/ai_agent_watchdog.py +450 -0
  103. crackerjack/monitoring/regression_prevention.py +638 -0
  104. crackerjack/orchestration/__init__.py +0 -0
  105. crackerjack/orchestration/advanced_orchestrator.py +970 -0
  106. crackerjack/orchestration/execution_strategies.py +341 -0
  107. crackerjack/orchestration/test_progress_streamer.py +636 -0
  108. crackerjack/plugins/__init__.py +15 -0
  109. crackerjack/plugins/base.py +200 -0
  110. crackerjack/plugins/hooks.py +246 -0
  111. crackerjack/plugins/loader.py +335 -0
  112. crackerjack/plugins/managers.py +259 -0
  113. crackerjack/py313.py +8 -3
  114. crackerjack/services/__init__.py +22 -0
  115. crackerjack/services/cache.py +314 -0
  116. crackerjack/services/config.py +347 -0
  117. crackerjack/services/config_integrity.py +99 -0
  118. crackerjack/services/contextual_ai_assistant.py +516 -0
  119. crackerjack/services/coverage_ratchet.py +347 -0
  120. crackerjack/services/debug.py +736 -0
  121. crackerjack/services/dependency_monitor.py +617 -0
  122. crackerjack/services/enhanced_filesystem.py +439 -0
  123. crackerjack/services/file_hasher.py +151 -0
  124. crackerjack/services/filesystem.py +395 -0
  125. crackerjack/services/git.py +165 -0
  126. crackerjack/services/health_metrics.py +611 -0
  127. crackerjack/services/initialization.py +847 -0
  128. crackerjack/services/log_manager.py +286 -0
  129. crackerjack/services/logging.py +174 -0
  130. crackerjack/services/metrics.py +578 -0
  131. crackerjack/services/pattern_cache.py +362 -0
  132. crackerjack/services/pattern_detector.py +515 -0
  133. crackerjack/services/performance_benchmarks.py +653 -0
  134. crackerjack/services/security.py +163 -0
  135. crackerjack/services/server_manager.py +234 -0
  136. crackerjack/services/smart_scheduling.py +144 -0
  137. crackerjack/services/tool_version_service.py +61 -0
  138. crackerjack/services/unified_config.py +437 -0
  139. crackerjack/services/version_checker.py +248 -0
  140. crackerjack/slash_commands/__init__.py +14 -0
  141. crackerjack/slash_commands/init.md +122 -0
  142. crackerjack/slash_commands/run.md +163 -0
  143. crackerjack/slash_commands/status.md +127 -0
  144. crackerjack-0.31.4.dist-info/METADATA +742 -0
  145. crackerjack-0.31.4.dist-info/RECORD +148 -0
  146. crackerjack-0.31.4.dist-info/entry_points.txt +2 -0
  147. crackerjack/.gitignore +0 -34
  148. crackerjack/.libcst.codemod.yaml +0 -18
  149. crackerjack/.pdm.toml +0 -1
  150. crackerjack/.pre-commit-config-ai.yaml +0 -149
  151. crackerjack/.pre-commit-config-fast.yaml +0 -69
  152. crackerjack/.pre-commit-config.yaml +0 -114
  153. crackerjack/crackerjack.py +0 -4140
  154. crackerjack/pyproject.toml +0 -285
  155. crackerjack-0.29.0.dist-info/METADATA +0 -1289
  156. crackerjack-0.29.0.dist-info/RECORD +0 -17
  157. {crackerjack-0.29.0.dist-info → crackerjack-0.31.4.dist-info}/WHEEL +0 -0
  158. {crackerjack-0.29.0.dist-info → crackerjack-0.31.4.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,345 @@
1
+ import os
2
+ import subprocess
3
+ import time
4
+ import typing as t
5
+ from concurrent.futures import ThreadPoolExecutor, as_completed
6
+ from dataclasses import dataclass
7
+ from pathlib import Path
8
+
9
+ from rich.console import Console
10
+
11
+ from crackerjack.config.hooks import HookDefinition, HookStrategy, RetryPolicy
12
+ from crackerjack.models.task import HookResult
13
+
14
+
15
+ @dataclass
16
+ class HookExecutionResult:
17
+ strategy_name: str
18
+ results: list[HookResult]
19
+ total_duration: float
20
+ success: bool
21
+ concurrent_execution: bool = False
22
+ cache_hits: int = 0
23
+ cache_misses: int = 0
24
+
25
+ @property
26
+ def failed_count(self) -> int:
27
+ return sum(1 for r in self.results if r.status == "failed")
28
+
29
+ @property
30
+ def passed_count(self) -> int:
31
+ return sum(1 for r in self.results if r.status == "passed")
32
+
33
+ @property
34
+ def cache_hit_rate(self) -> float:
35
+ total_requests = self.cache_hits + self.cache_misses
36
+ return (self.cache_hits / total_requests * 100) if total_requests > 0 else 0.0
37
+
38
+ @property
39
+ def performance_summary(self) -> dict[str, t.Any]:
40
+ return {
41
+ "total_hooks": len(self.results),
42
+ "passed": self.passed_count,
43
+ "failed": self.failed_count,
44
+ "duration_seconds": round(self.total_duration, 2),
45
+ "concurrent": self.concurrent_execution,
46
+ "cache_hits": self.cache_hits,
47
+ "cache_misses": self.cache_misses,
48
+ "cache_hit_rate_percent": round(self.cache_hit_rate, 1),
49
+ }
50
+
51
+
52
+ class HookExecutor:
53
+ def __init__(
54
+ self,
55
+ console: Console,
56
+ pkg_path: Path,
57
+ verbose: bool = False,
58
+ quiet: bool = False,
59
+ ) -> None:
60
+ self.console = console
61
+ self.pkg_path = pkg_path
62
+ self.verbose = verbose
63
+ self.quiet = quiet
64
+
65
+ def execute_strategy(self, strategy: HookStrategy) -> HookExecutionResult:
66
+ start_time = time.time()
67
+
68
+ self._print_strategy_header(strategy)
69
+
70
+ if strategy.parallel and len(strategy.hooks) > 1:
71
+ results = self._execute_parallel(strategy)
72
+ else:
73
+ results = self._execute_sequential(strategy)
74
+
75
+ if strategy.retry_policy != RetryPolicy.NONE:
76
+ results = self._handle_retries(strategy, results)
77
+
78
+ total_duration = time.time() - start_time
79
+ success = all(r.status == "passed" for r in results)
80
+
81
+ if not self.quiet:
82
+ self._print_summary(strategy, results, success)
83
+
84
+ return HookExecutionResult(
85
+ strategy_name=strategy.name,
86
+ results=results,
87
+ total_duration=total_duration,
88
+ success=success,
89
+ )
90
+
91
+ def _print_strategy_header(self, strategy: HookStrategy) -> None:
92
+ self.console.print("\n" + "-" * 80)
93
+ if strategy.name == "fast":
94
+ self.console.print(
95
+ "[bold bright_cyan]🔍 HOOKS[/bold bright_cyan] [bold bright_white]Running code quality checks[/bold bright_white]",
96
+ )
97
+ elif strategy.name == "comprehensive":
98
+ self.console.print(
99
+ "[bold bright_cyan]🔍 HOOKS[/bold bright_cyan] [bold bright_white]Running comprehensive quality checks[/bold bright_white]",
100
+ )
101
+ else:
102
+ self.console.print(
103
+ f"[bold bright_cyan]🔍 HOOKS[/bold bright_cyan] [bold bright_white]Running {strategy.name} hooks[/bold bright_white]",
104
+ )
105
+ self.console.print("-" * 80 + "\n")
106
+
107
+ def _execute_sequential(self, strategy: HookStrategy) -> list[HookResult]:
108
+ results: list[HookResult] = []
109
+ for hook in strategy.hooks:
110
+ result = self.execute_single_hook(hook)
111
+ results.append(result)
112
+ self._display_hook_result(result)
113
+ return results
114
+
115
+ def _execute_parallel(self, strategy: HookStrategy) -> list[HookResult]:
116
+ results: list[HookResult] = []
117
+
118
+ formatting_hooks = [h for h in strategy.hooks if h.is_formatting]
119
+ other_hooks = [h for h in strategy.hooks if not h.is_formatting]
120
+
121
+ for hook in formatting_hooks:
122
+ result = self.execute_single_hook(hook)
123
+ results.append(result)
124
+ self._display_hook_result(result)
125
+
126
+ if other_hooks:
127
+ with ThreadPoolExecutor(max_workers=strategy.max_workers) as executor:
128
+ future_to_hook = {
129
+ executor.submit(self.execute_single_hook, hook): hook
130
+ for hook in other_hooks
131
+ }
132
+
133
+ for future in as_completed(future_to_hook):
134
+ try:
135
+ result = future.result()
136
+ results.append(result)
137
+ self._display_hook_result(result)
138
+ except Exception as e:
139
+ hook = future_to_hook[future]
140
+ error_result = HookResult(
141
+ id=hook.name,
142
+ name=hook.name,
143
+ status="error",
144
+ duration=0.0,
145
+ issues_found=[str(e)],
146
+ stage=hook.stage.value,
147
+ )
148
+ results.append(error_result)
149
+ self._display_hook_result(error_result)
150
+
151
+ return results
152
+
153
+ def execute_single_hook(self, hook: HookDefinition) -> HookResult:
154
+ start_time = time.time()
155
+
156
+ try:
157
+ clean_env = self._get_clean_environment()
158
+ result = subprocess.run(
159
+ hook.get_command(),
160
+ check=False,
161
+ cwd=self.pkg_path,
162
+ text=True,
163
+ timeout=hook.timeout,
164
+ env=clean_env,
165
+ capture_output=True,
166
+ )
167
+
168
+ duration = time.time() - start_time
169
+
170
+ # Only display raw output in verbose mode, and only for failed hooks
171
+ if result.returncode != 0 and self.verbose:
172
+ if result.stdout:
173
+ self.console.print(result.stdout)
174
+ if result.stderr:
175
+ self.console.print(result.stderr)
176
+
177
+ return HookResult(
178
+ id=hook.name,
179
+ name=hook.name,
180
+ status="passed" if result.returncode == 0 else "failed",
181
+ duration=duration,
182
+ files_processed=0,
183
+ issues_found=[]
184
+ if result.returncode == 0
185
+ else [f"Hook failed with code {result.returncode}"],
186
+ stage=hook.stage.value,
187
+ )
188
+
189
+ except subprocess.TimeoutExpired:
190
+ duration = time.time() - start_time
191
+ return HookResult(
192
+ id=hook.name,
193
+ name=hook.name,
194
+ status="timeout",
195
+ duration=duration,
196
+ issues_found=[f"Hook timed out after {duration:.1f}s"],
197
+ stage=hook.stage.value,
198
+ )
199
+ except Exception as e:
200
+ duration = time.time() - start_time
201
+ return HookResult(
202
+ id=hook.name,
203
+ name=hook.name,
204
+ status="error",
205
+ duration=duration,
206
+ issues_found=[str(e)],
207
+ stage=hook.stage.value,
208
+ )
209
+
210
+ def _parse_hook_output(
211
+ self,
212
+ result: subprocess.CompletedProcess[str],
213
+ ) -> dict[str, t.Any]:
214
+ output = result.stdout + result.stderr
215
+ return {
216
+ "hook_id": None,
217
+ "exit_code": result.returncode,
218
+ "files_processed": 0,
219
+ "issues": [],
220
+ "raw_output": output,
221
+ }
222
+
223
+ def _display_hook_result(self, result: HookResult) -> None:
224
+ status_icon = "✅" if result.status == "passed" else "❌"
225
+
226
+ # Create dot-filled line like classic pre-commit format
227
+ max_width = 70 # Leave room for terminal margins
228
+
229
+ if len(result.name) > max_width:
230
+ # Truncate long names
231
+ line = result.name[: max_width - 3] + "..."
232
+ else:
233
+ # Fill with dots to reach max width
234
+ dots_needed = max_width - len(result.name)
235
+ line = result.name + ("." * dots_needed)
236
+
237
+ self.console.print(f"{line} {status_icon}")
238
+
239
+ def _handle_retries(
240
+ self,
241
+ strategy: HookStrategy,
242
+ results: list[HookResult],
243
+ ) -> list[HookResult]:
244
+ if strategy.retry_policy == RetryPolicy.FORMATTING_ONLY:
245
+ return self._retry_formatting_hooks(strategy, results)
246
+ if strategy.retry_policy == RetryPolicy.ALL_HOOKS:
247
+ return self._retry_all_hooks(strategy, results)
248
+ return results
249
+
250
+ def _retry_formatting_hooks(
251
+ self,
252
+ strategy: HookStrategy,
253
+ results: list[HookResult],
254
+ ) -> list[HookResult]:
255
+ formatting_hooks_failed: set[str] = set()
256
+
257
+ for i, result in enumerate(results):
258
+ hook = strategy.hooks[i]
259
+ if hook.is_formatting and result.status == "failed":
260
+ formatting_hooks_failed.add(hook.name)
261
+
262
+ if not formatting_hooks_failed:
263
+ return results
264
+
265
+ updated_results: list[HookResult] = []
266
+ for i, hook in enumerate(strategy.hooks):
267
+ prev_result = results[i]
268
+ new_result = self.execute_single_hook(hook)
269
+
270
+ new_result.duration += prev_result.duration
271
+ updated_results.append(new_result)
272
+ self._display_hook_result(new_result)
273
+
274
+ return updated_results
275
+
276
+ def _retry_all_hooks(
277
+ self,
278
+ strategy: HookStrategy,
279
+ results: list[HookResult],
280
+ ) -> list[HookResult]:
281
+ failed_hooks = [i for i, r in enumerate(results) if r.status == "failed"]
282
+
283
+ if not failed_hooks:
284
+ return results
285
+
286
+ updated_results: list[HookResult] = results.copy()
287
+ for i in failed_hooks:
288
+ hook = strategy.hooks[i]
289
+ prev_result = results[i]
290
+ new_result = self.execute_single_hook(hook)
291
+
292
+ new_result.duration += prev_result.duration
293
+ updated_results[i] = new_result
294
+ self._display_hook_result(new_result)
295
+
296
+ return updated_results
297
+
298
+ def _get_clean_environment(self) -> dict[str, str]:
299
+ clean_env = {
300
+ "HOME": os.environ.get("HOME", ""),
301
+ "USER": os.environ.get("USER", ""),
302
+ "SHELL": os.environ.get("SHELL", "/bin/bash"),
303
+ "LANG": os.environ.get("LANG", "en_US.UTF-8"),
304
+ "LC_ALL": os.environ.get("LC_ALL", ""),
305
+ "TERM": os.environ.get("TERM", "xterm-256color"),
306
+ }
307
+
308
+ system_path = os.environ.get("PATH", "")
309
+ if system_path:
310
+ venv_bin = str(Path(self.pkg_path) / ".venv" / "bin")
311
+ path_parts = [p for p in system_path.split(":") if p != venv_bin]
312
+ clean_env["PATH"] = ":".join(path_parts)
313
+
314
+ python_vars_to_exclude = {
315
+ "VIRTUAL_ENV",
316
+ "PYTHONPATH",
317
+ "PYTHON_PATH",
318
+ "PIP_CONFIG_FILE",
319
+ "PYTHONHOME",
320
+ "CONDA_DEFAULT_ENV",
321
+ }
322
+
323
+ for key, value in os.environ.items():
324
+ if key not in python_vars_to_exclude and key not in clean_env:
325
+ if not key.startswith(("PYTHON", "PIP_", "CONDA_", "VIRTUAL_")):
326
+ clean_env[key] = value
327
+
328
+ return clean_env
329
+
330
+ def _print_summary(
331
+ self,
332
+ strategy: HookStrategy,
333
+ results: list[HookResult],
334
+ success: bool,
335
+ ) -> None:
336
+ if success:
337
+ self.console.print(
338
+ f"[green]✅[/green] {strategy.name.title()} hooks passed: {len(results)} / {len(results)}",
339
+ )
340
+ else:
341
+ failed_count = sum(1 for r in results if r.status == "failed")
342
+ error_count = sum(1 for r in results if r.status in ("timeout", "error"))
343
+ self.console.print(
344
+ f"[red]❌[/red] {strategy.name.title()} hooks failed: {failed_count} failed, {error_count} errors",
345
+ )