crackerjack 0.30.3__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.
- crackerjack/CLAUDE.md +1005 -0
- crackerjack/RULES.md +380 -0
- crackerjack/__init__.py +42 -13
- crackerjack/__main__.py +225 -299
- crackerjack/agents/__init__.py +41 -0
- crackerjack/agents/architect_agent.py +281 -0
- crackerjack/agents/base.py +169 -0
- crackerjack/agents/coordinator.py +512 -0
- crackerjack/agents/documentation_agent.py +498 -0
- crackerjack/agents/dry_agent.py +388 -0
- crackerjack/agents/formatting_agent.py +245 -0
- crackerjack/agents/import_optimization_agent.py +281 -0
- crackerjack/agents/performance_agent.py +669 -0
- crackerjack/agents/proactive_agent.py +104 -0
- crackerjack/agents/refactoring_agent.py +788 -0
- crackerjack/agents/security_agent.py +529 -0
- crackerjack/agents/test_creation_agent.py +652 -0
- crackerjack/agents/test_specialist_agent.py +486 -0
- crackerjack/agents/tracker.py +212 -0
- crackerjack/api.py +560 -0
- crackerjack/cli/__init__.py +24 -0
- crackerjack/cli/facade.py +104 -0
- crackerjack/cli/handlers.py +267 -0
- crackerjack/cli/interactive.py +471 -0
- crackerjack/cli/options.py +401 -0
- crackerjack/cli/utils.py +18 -0
- crackerjack/code_cleaner.py +618 -928
- crackerjack/config/__init__.py +19 -0
- crackerjack/config/hooks.py +218 -0
- crackerjack/core/__init__.py +0 -0
- crackerjack/core/async_workflow_orchestrator.py +406 -0
- crackerjack/core/autofix_coordinator.py +200 -0
- crackerjack/core/container.py +104 -0
- crackerjack/core/enhanced_container.py +542 -0
- crackerjack/core/performance.py +243 -0
- crackerjack/core/phase_coordinator.py +561 -0
- crackerjack/core/proactive_workflow.py +316 -0
- crackerjack/core/session_coordinator.py +289 -0
- crackerjack/core/workflow_orchestrator.py +640 -0
- crackerjack/dynamic_config.py +94 -103
- crackerjack/errors.py +263 -41
- crackerjack/executors/__init__.py +11 -0
- crackerjack/executors/async_hook_executor.py +431 -0
- crackerjack/executors/cached_hook_executor.py +242 -0
- crackerjack/executors/hook_executor.py +345 -0
- crackerjack/executors/individual_hook_executor.py +669 -0
- crackerjack/intelligence/__init__.py +44 -0
- crackerjack/intelligence/adaptive_learning.py +751 -0
- crackerjack/intelligence/agent_orchestrator.py +551 -0
- crackerjack/intelligence/agent_registry.py +414 -0
- crackerjack/intelligence/agent_selector.py +502 -0
- crackerjack/intelligence/integration.py +290 -0
- crackerjack/interactive.py +576 -315
- crackerjack/managers/__init__.py +11 -0
- crackerjack/managers/async_hook_manager.py +135 -0
- crackerjack/managers/hook_manager.py +137 -0
- crackerjack/managers/publish_manager.py +411 -0
- crackerjack/managers/test_command_builder.py +151 -0
- crackerjack/managers/test_executor.py +435 -0
- crackerjack/managers/test_manager.py +258 -0
- crackerjack/managers/test_manager_backup.py +1124 -0
- crackerjack/managers/test_progress.py +144 -0
- crackerjack/mcp/__init__.py +0 -0
- crackerjack/mcp/cache.py +336 -0
- crackerjack/mcp/client_runner.py +104 -0
- crackerjack/mcp/context.py +615 -0
- crackerjack/mcp/dashboard.py +636 -0
- crackerjack/mcp/enhanced_progress_monitor.py +479 -0
- crackerjack/mcp/file_monitor.py +336 -0
- crackerjack/mcp/progress_components.py +569 -0
- crackerjack/mcp/progress_monitor.py +949 -0
- crackerjack/mcp/rate_limiter.py +332 -0
- crackerjack/mcp/server.py +22 -0
- crackerjack/mcp/server_core.py +244 -0
- crackerjack/mcp/service_watchdog.py +501 -0
- crackerjack/mcp/state.py +395 -0
- crackerjack/mcp/task_manager.py +257 -0
- crackerjack/mcp/tools/__init__.py +17 -0
- crackerjack/mcp/tools/core_tools.py +249 -0
- crackerjack/mcp/tools/error_analyzer.py +308 -0
- crackerjack/mcp/tools/execution_tools.py +370 -0
- crackerjack/mcp/tools/execution_tools_backup.py +1097 -0
- crackerjack/mcp/tools/intelligence_tool_registry.py +80 -0
- crackerjack/mcp/tools/intelligence_tools.py +314 -0
- crackerjack/mcp/tools/monitoring_tools.py +502 -0
- crackerjack/mcp/tools/proactive_tools.py +384 -0
- crackerjack/mcp/tools/progress_tools.py +141 -0
- crackerjack/mcp/tools/utility_tools.py +341 -0
- crackerjack/mcp/tools/workflow_executor.py +360 -0
- crackerjack/mcp/websocket/__init__.py +14 -0
- crackerjack/mcp/websocket/app.py +39 -0
- crackerjack/mcp/websocket/endpoints.py +559 -0
- crackerjack/mcp/websocket/jobs.py +253 -0
- crackerjack/mcp/websocket/server.py +116 -0
- crackerjack/mcp/websocket/websocket_handler.py +78 -0
- crackerjack/mcp/websocket_server.py +10 -0
- crackerjack/models/__init__.py +31 -0
- crackerjack/models/config.py +93 -0
- crackerjack/models/config_adapter.py +230 -0
- crackerjack/models/protocols.py +118 -0
- crackerjack/models/task.py +154 -0
- crackerjack/monitoring/ai_agent_watchdog.py +450 -0
- crackerjack/monitoring/regression_prevention.py +638 -0
- crackerjack/orchestration/__init__.py +0 -0
- crackerjack/orchestration/advanced_orchestrator.py +970 -0
- crackerjack/orchestration/execution_strategies.py +341 -0
- crackerjack/orchestration/test_progress_streamer.py +636 -0
- crackerjack/plugins/__init__.py +15 -0
- crackerjack/plugins/base.py +200 -0
- crackerjack/plugins/hooks.py +246 -0
- crackerjack/plugins/loader.py +335 -0
- crackerjack/plugins/managers.py +259 -0
- crackerjack/py313.py +8 -3
- crackerjack/services/__init__.py +22 -0
- crackerjack/services/cache.py +314 -0
- crackerjack/services/config.py +347 -0
- crackerjack/services/config_integrity.py +99 -0
- crackerjack/services/contextual_ai_assistant.py +516 -0
- crackerjack/services/coverage_ratchet.py +347 -0
- crackerjack/services/debug.py +736 -0
- crackerjack/services/dependency_monitor.py +617 -0
- crackerjack/services/enhanced_filesystem.py +439 -0
- crackerjack/services/file_hasher.py +151 -0
- crackerjack/services/filesystem.py +395 -0
- crackerjack/services/git.py +165 -0
- crackerjack/services/health_metrics.py +611 -0
- crackerjack/services/initialization.py +847 -0
- crackerjack/services/log_manager.py +286 -0
- crackerjack/services/logging.py +174 -0
- crackerjack/services/metrics.py +578 -0
- crackerjack/services/pattern_cache.py +362 -0
- crackerjack/services/pattern_detector.py +515 -0
- crackerjack/services/performance_benchmarks.py +653 -0
- crackerjack/services/security.py +163 -0
- crackerjack/services/server_manager.py +234 -0
- crackerjack/services/smart_scheduling.py +144 -0
- crackerjack/services/tool_version_service.py +61 -0
- crackerjack/services/unified_config.py +437 -0
- crackerjack/services/version_checker.py +248 -0
- crackerjack/slash_commands/__init__.py +14 -0
- crackerjack/slash_commands/init.md +122 -0
- crackerjack/slash_commands/run.md +163 -0
- crackerjack/slash_commands/status.md +127 -0
- crackerjack-0.31.4.dist-info/METADATA +742 -0
- crackerjack-0.31.4.dist-info/RECORD +148 -0
- crackerjack-0.31.4.dist-info/entry_points.txt +2 -0
- crackerjack/.gitignore +0 -34
- crackerjack/.libcst.codemod.yaml +0 -18
- crackerjack/.pdm.toml +0 -1
- crackerjack/crackerjack.py +0 -3805
- crackerjack/pyproject.toml +0 -286
- crackerjack-0.30.3.dist-info/METADATA +0 -1290
- crackerjack-0.30.3.dist-info/RECORD +0 -16
- {crackerjack-0.30.3.dist-info โ crackerjack-0.31.4.dist-info}/WHEEL +0 -0
- {crackerjack-0.30.3.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)
|