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,336 @@
1
+ import asyncio
2
+ import json
3
+ import time
4
+ from collections.abc import Callable
5
+ from pathlib import Path
6
+
7
+ try:
8
+ from watchdog.events import FileSystemEvent, FileSystemEventHandler
9
+ from watchdog.observers import Observer
10
+
11
+ WATCHDOG_AVAILABLE = True
12
+ except ImportError:
13
+ FileSystemEvent = None
14
+ FileSystemEventHandler = None
15
+ Observer = None
16
+ WATCHDOG_AVAILABLE = False
17
+
18
+ import contextlib
19
+
20
+ from rich.console import Console
21
+
22
+ console = Console()
23
+
24
+
25
+ if WATCHDOG_AVAILABLE:
26
+
27
+ class ProgressFileHandler(FileSystemEventHandler):
28
+ def __init__(self, callback: Callable[[str, dict], None]) -> None:
29
+ super().__init__()
30
+ self.callback = callback
31
+ self._last_processed: dict[str, float] = {}
32
+ self._debounce_delay = 0.1
33
+
34
+ def on_modified(self, event: FileSystemEvent) -> None:
35
+ if event.is_directory:
36
+ return
37
+
38
+ file_path = Path(event.src_path)
39
+
40
+ if not file_path.name.startswith("job - ") or file_path.suffix != ".json":
41
+ return
42
+
43
+ now = time.time()
44
+ if file_path.name in self._last_processed:
45
+ if now - self._last_processed[file_path.name] < self._debounce_delay:
46
+ return
47
+
48
+ self._last_processed[file_path.name] = now
49
+
50
+ job_id = file_path.stem.replace("job - ", "")
51
+
52
+ try:
53
+ with file_path.open() as f:
54
+ progress_data = json.load(f)
55
+
56
+ self.callback(job_id, progress_data)
57
+
58
+ except (json.JSONDecodeError, FileNotFoundError, OSError) as e:
59
+ console.print(
60
+ f"[yellow]Warning: Failed to read progress file {file_path}: {e}[/yellow]",
61
+ )
62
+
63
+ def on_created(self, event: FileSystemEvent) -> None:
64
+ self.on_modified(event)
65
+ else:
66
+
67
+ class ProgressFileHandler:
68
+ def __init__(self, callback: Callable[[str, dict], None]) -> None:
69
+ pass
70
+
71
+
72
+ class AsyncProgressMonitor:
73
+ def __init__(self, progress_dir: Path) -> None:
74
+ self.progress_dir = progress_dir
75
+ self.observer: Observer | None = None
76
+ self.subscribers: dict[str, set[Callable[[dict], None]]] = {}
77
+ self._running = False
78
+
79
+ self.progress_dir.mkdir(exist_ok=True)
80
+
81
+ if not WATCHDOG_AVAILABLE:
82
+ console.print(
83
+ "[yellow]Warning: watchdog not available, falling back to polling[/yellow]",
84
+ )
85
+
86
+ async def start(self) -> None:
87
+ if not WATCHDOG_AVAILABLE:
88
+ return
89
+
90
+ self._running = True
91
+
92
+ handler = ProgressFileHandler(self._on_file_changed)
93
+
94
+ self.observer = Observer()
95
+ self.observer.schedule(handler, str(self.progress_dir), recursive=False)
96
+ self.observer.start()
97
+
98
+ console.print(
99
+ f"[green]๐Ÿ“ Started monitoring progress directory: {self.progress_dir}[/green]",
100
+ )
101
+
102
+ async def stop(self) -> None:
103
+ self._running = False
104
+
105
+ if self.observer:
106
+ self.observer.stop()
107
+ self.observer.join()
108
+ self.observer = None
109
+
110
+ console.print("[yellow]๐Ÿ“ Stopped progress directory monitoring[/yellow]")
111
+
112
+ def subscribe(self, job_id: str, callback: Callable[[dict], None]) -> None:
113
+ if job_id not in self.subscribers:
114
+ self.subscribers[job_id] = set()
115
+
116
+ self.subscribers[job_id].add(callback)
117
+ console.print(f"[cyan]๐Ÿ“‹ Subscribed to job updates: {job_id}[/cyan]")
118
+
119
+ def unsubscribe(self, job_id: str, callback: Callable[[dict], None]) -> None:
120
+ if job_id in self.subscribers:
121
+ self.subscribers[job_id].discard(callback)
122
+
123
+ if not self.subscribers[job_id]:
124
+ del self.subscribers[job_id]
125
+
126
+ console.print(f"[cyan]๐Ÿ“‹ Unsubscribed from job updates: {job_id}[/cyan]")
127
+
128
+ def _on_file_changed(self, job_id: str, progress_data: dict) -> None:
129
+ if job_id in self.subscribers:
130
+ for callback in self.subscribers[job_id].copy():
131
+ try:
132
+ callback(progress_data)
133
+ except Exception as e:
134
+ console.print(
135
+ f"[red]Error in progress callback for job {job_id}: {e}[/red]",
136
+ )
137
+
138
+ self.subscribers[job_id].discard(callback)
139
+
140
+ async def get_current_progress(self, job_id: str) -> dict | None:
141
+ progress_file = self.progress_dir / f"job - {job_id}.json"
142
+
143
+ if not progress_file.exists():
144
+ return None
145
+
146
+ try:
147
+ with progress_file.open() as f:
148
+ return json.load(f)
149
+ except (json.JSONDecodeError, OSError):
150
+ return None
151
+
152
+ async def cleanup_completed_jobs(self, max_age_minutes: int = 60) -> int:
153
+ if not self.progress_dir.exists():
154
+ return 0
155
+
156
+ cleaned = 0
157
+ cutoff_time = time.time() - (max_age_minutes * 60)
158
+
159
+ for progress_file in self.progress_dir.glob("job -* .json"):
160
+ try:
161
+ if progress_file.stat().st_mtime < cutoff_time:
162
+ with progress_file.open() as f:
163
+ data = json.load(f)
164
+
165
+ if data.get("status") in ("completed", "failed"):
166
+ progress_file.unlink()
167
+ cleaned += 1
168
+ console.print(
169
+ f"[dim]๐Ÿงน Cleaned up old progress file: {progress_file.name}[/dim]",
170
+ )
171
+
172
+ except (json.JSONDecodeError, OSError, KeyError):
173
+ from contextlib import suppress
174
+
175
+ with suppress(OSError):
176
+ progress_file.unlink()
177
+ cleaned += 1
178
+ console.print(
179
+ f"[dim]๐Ÿงน Removed corrupted progress file: {progress_file.name}[/dim]",
180
+ )
181
+
182
+ return cleaned
183
+
184
+
185
+ class PollingProgressMonitor:
186
+ def __init__(self, progress_dir: Path) -> None:
187
+ self.progress_dir = progress_dir
188
+ self.subscribers: dict[str, set[Callable[[dict], None]]] = {}
189
+ self._running = False
190
+ self._poll_task: asyncio.Task | None = None
191
+ self._file_mtimes: dict[str, float] = {}
192
+
193
+ self.progress_dir.mkdir(exist_ok=True)
194
+
195
+ async def start(self) -> None:
196
+ self._running = True
197
+ self._poll_task = asyncio.create_task(self._poll_loop())
198
+ console.print(
199
+ f"[yellow]๐Ÿ“ Started polling progress directory: {self.progress_dir}[/yellow]",
200
+ )
201
+
202
+ async def stop(self) -> None:
203
+ self._running = False
204
+
205
+ if self._poll_task:
206
+ self._poll_task.cancel()
207
+ with contextlib.suppress(asyncio.CancelledError):
208
+ await self._poll_task
209
+ self._poll_task = None
210
+
211
+ console.print("[yellow]๐Ÿ“ Stopped progress directory polling[/yellow]")
212
+
213
+ async def _poll_loop(self) -> None:
214
+ while self._running:
215
+ try:
216
+ await self._check_files()
217
+ await asyncio.sleep(0.5)
218
+ except asyncio.CancelledError:
219
+ break
220
+ except Exception as e:
221
+ console.print(f"[red]Error in polling loop: {e}[/red]")
222
+ await asyncio.sleep(1)
223
+
224
+ async def _check_files(self) -> None:
225
+ if not self.progress_dir.exists():
226
+ return
227
+
228
+ current_files = {}
229
+
230
+ for progress_file in self.progress_dir.glob("job -* .json"):
231
+ try:
232
+ mtime = progress_file.stat().st_mtime
233
+ current_files[progress_file.name] = mtime
234
+
235
+ if (
236
+ progress_file.name not in self._file_mtimes
237
+ or mtime > self._file_mtimes[progress_file.name]
238
+ ):
239
+ job_id = progress_file.stem.replace("job - ", "")
240
+
241
+ try:
242
+ with progress_file.open() as f:
243
+ progress_data = json.load(f)
244
+
245
+ self._notify_subscribers(job_id, progress_data)
246
+
247
+ except (json.JSONDecodeError, OSError) as e:
248
+ console.print(
249
+ f"[yellow]Warning: Failed to read progress file {progress_file}: {e}[/yellow]",
250
+ )
251
+
252
+ except OSError:
253
+ continue
254
+
255
+ self._file_mtimes = current_files
256
+
257
+ def _notify_subscribers(self, job_id: str, progress_data: dict) -> None:
258
+ if job_id in self.subscribers:
259
+ for callback in self.subscribers[job_id].copy():
260
+ try:
261
+ callback(progress_data)
262
+ except Exception as e:
263
+ console.print(
264
+ f"[red]Error in progress callback for job {job_id}: {e}[/red]",
265
+ )
266
+ self.subscribers[job_id].discard(callback)
267
+
268
+ def subscribe(self, job_id: str, callback: Callable[[dict], None]) -> None:
269
+ if job_id not in self.subscribers:
270
+ self.subscribers[job_id] = set()
271
+
272
+ self.subscribers[job_id].add(callback)
273
+ console.print(f"[cyan]๐Ÿ“‹ Subscribed to job updates: {job_id} (polling)[/cyan]")
274
+
275
+ def unsubscribe(self, job_id: str, callback: Callable[[dict], None]) -> None:
276
+ if job_id in self.subscribers:
277
+ self.subscribers[job_id].discard(callback)
278
+
279
+ if not self.subscribers[job_id]:
280
+ del self.subscribers[job_id]
281
+
282
+ console.print(
283
+ f"[cyan]๐Ÿ“‹ Unsubscribed from job updates: {job_id} (polling)[/cyan]",
284
+ )
285
+
286
+ async def get_current_progress(self, job_id: str) -> dict | None:
287
+ progress_file = self.progress_dir / f"job - {job_id}.json"
288
+
289
+ if not progress_file.exists():
290
+ return None
291
+
292
+ try:
293
+ with progress_file.open() as f:
294
+ return json.load(f)
295
+ except (json.JSONDecodeError, OSError):
296
+ return None
297
+
298
+ async def cleanup_completed_jobs(self, max_age_minutes: int = 60) -> int:
299
+ if not self.progress_dir.exists():
300
+ return 0
301
+
302
+ cleaned = 0
303
+ cutoff_time = time.time() - (max_age_minutes * 60)
304
+
305
+ for progress_file in self.progress_dir.glob("job -* .json"):
306
+ try:
307
+ if progress_file.stat().st_mtime < cutoff_time:
308
+ with progress_file.open() as f:
309
+ data = json.load(f)
310
+
311
+ if data.get("status") in ("completed", "failed"):
312
+ progress_file.unlink()
313
+ cleaned += 1
314
+ console.print(
315
+ f"[dim]๐Ÿงน Cleaned up old progress file: {progress_file.name}[/dim]",
316
+ )
317
+
318
+ except (json.JSONDecodeError, OSError, KeyError):
319
+ from contextlib import suppress
320
+
321
+ with suppress(OSError):
322
+ progress_file.unlink()
323
+ cleaned += 1
324
+ console.print(
325
+ f"[dim]๐Ÿงน Removed corrupted progress file: {progress_file.name}[/dim]",
326
+ )
327
+
328
+ return cleaned
329
+
330
+
331
+ def create_progress_monitor(
332
+ progress_dir: Path,
333
+ ) -> AsyncProgressMonitor | PollingProgressMonitor:
334
+ if WATCHDOG_AVAILABLE:
335
+ return AsyncProgressMonitor(progress_dir)
336
+ return PollingProgressMonitor(progress_dir)