crackerjack 0.31.10__py3-none-any.whl → 0.31.12__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of crackerjack might be problematic. Click here for more details.
- crackerjack/CLAUDE.md +288 -705
- crackerjack/__main__.py +22 -8
- crackerjack/agents/__init__.py +0 -3
- crackerjack/agents/architect_agent.py +0 -43
- crackerjack/agents/base.py +1 -9
- crackerjack/agents/coordinator.py +2 -148
- crackerjack/agents/documentation_agent.py +109 -81
- crackerjack/agents/dry_agent.py +122 -97
- crackerjack/agents/formatting_agent.py +3 -16
- crackerjack/agents/import_optimization_agent.py +1174 -130
- crackerjack/agents/performance_agent.py +956 -188
- crackerjack/agents/performance_helpers.py +229 -0
- crackerjack/agents/proactive_agent.py +1 -48
- crackerjack/agents/refactoring_agent.py +516 -246
- crackerjack/agents/refactoring_helpers.py +282 -0
- crackerjack/agents/security_agent.py +393 -90
- crackerjack/agents/test_creation_agent.py +1776 -120
- crackerjack/agents/test_specialist_agent.py +59 -15
- crackerjack/agents/tracker.py +0 -102
- crackerjack/api.py +145 -37
- crackerjack/cli/handlers.py +48 -30
- crackerjack/cli/interactive.py +11 -11
- crackerjack/cli/options.py +66 -4
- crackerjack/code_cleaner.py +808 -148
- crackerjack/config/global_lock_config.py +110 -0
- crackerjack/config/hooks.py +43 -64
- crackerjack/core/async_workflow_orchestrator.py +247 -97
- crackerjack/core/autofix_coordinator.py +192 -109
- crackerjack/core/enhanced_container.py +46 -63
- crackerjack/core/file_lifecycle.py +549 -0
- crackerjack/core/performance.py +9 -8
- crackerjack/core/performance_monitor.py +395 -0
- crackerjack/core/phase_coordinator.py +281 -94
- crackerjack/core/proactive_workflow.py +9 -58
- crackerjack/core/resource_manager.py +501 -0
- crackerjack/core/service_watchdog.py +490 -0
- crackerjack/core/session_coordinator.py +4 -8
- crackerjack/core/timeout_manager.py +504 -0
- crackerjack/core/websocket_lifecycle.py +475 -0
- crackerjack/core/workflow_orchestrator.py +343 -209
- crackerjack/dynamic_config.py +47 -6
- crackerjack/errors.py +3 -4
- crackerjack/executors/async_hook_executor.py +63 -13
- crackerjack/executors/cached_hook_executor.py +14 -14
- crackerjack/executors/hook_executor.py +100 -37
- crackerjack/executors/hook_lock_manager.py +856 -0
- crackerjack/executors/individual_hook_executor.py +120 -86
- crackerjack/intelligence/__init__.py +0 -7
- crackerjack/intelligence/adaptive_learning.py +13 -86
- crackerjack/intelligence/agent_orchestrator.py +15 -78
- crackerjack/intelligence/agent_registry.py +12 -59
- crackerjack/intelligence/agent_selector.py +31 -92
- crackerjack/intelligence/integration.py +1 -41
- crackerjack/interactive.py +9 -9
- crackerjack/managers/async_hook_manager.py +25 -8
- crackerjack/managers/hook_manager.py +9 -9
- crackerjack/managers/publish_manager.py +57 -59
- crackerjack/managers/test_command_builder.py +6 -36
- crackerjack/managers/test_executor.py +9 -61
- crackerjack/managers/test_manager.py +17 -63
- crackerjack/managers/test_manager_backup.py +77 -127
- crackerjack/managers/test_progress.py +4 -23
- crackerjack/mcp/cache.py +5 -12
- crackerjack/mcp/client_runner.py +10 -10
- crackerjack/mcp/context.py +64 -6
- crackerjack/mcp/dashboard.py +14 -11
- crackerjack/mcp/enhanced_progress_monitor.py +55 -55
- crackerjack/mcp/file_monitor.py +72 -42
- crackerjack/mcp/progress_components.py +103 -84
- crackerjack/mcp/progress_monitor.py +122 -49
- crackerjack/mcp/rate_limiter.py +12 -12
- crackerjack/mcp/server_core.py +16 -22
- crackerjack/mcp/service_watchdog.py +26 -26
- crackerjack/mcp/state.py +15 -0
- crackerjack/mcp/tools/core_tools.py +95 -39
- crackerjack/mcp/tools/error_analyzer.py +6 -32
- crackerjack/mcp/tools/execution_tools.py +1 -56
- crackerjack/mcp/tools/execution_tools_backup.py +35 -131
- crackerjack/mcp/tools/intelligence_tool_registry.py +0 -36
- crackerjack/mcp/tools/intelligence_tools.py +2 -55
- crackerjack/mcp/tools/monitoring_tools.py +308 -145
- crackerjack/mcp/tools/proactive_tools.py +12 -42
- crackerjack/mcp/tools/progress_tools.py +23 -15
- crackerjack/mcp/tools/utility_tools.py +3 -40
- crackerjack/mcp/tools/workflow_executor.py +40 -60
- crackerjack/mcp/websocket/app.py +0 -3
- crackerjack/mcp/websocket/endpoints.py +206 -268
- crackerjack/mcp/websocket/jobs.py +213 -66
- crackerjack/mcp/websocket/server.py +84 -6
- crackerjack/mcp/websocket/websocket_handler.py +137 -29
- crackerjack/models/config_adapter.py +3 -16
- crackerjack/models/protocols.py +162 -3
- crackerjack/models/resource_protocols.py +454 -0
- crackerjack/models/task.py +3 -3
- crackerjack/monitoring/__init__.py +0 -0
- crackerjack/monitoring/ai_agent_watchdog.py +25 -71
- crackerjack/monitoring/regression_prevention.py +28 -87
- crackerjack/orchestration/advanced_orchestrator.py +44 -78
- crackerjack/orchestration/coverage_improvement.py +10 -60
- crackerjack/orchestration/execution_strategies.py +16 -16
- crackerjack/orchestration/test_progress_streamer.py +61 -53
- crackerjack/plugins/base.py +1 -1
- crackerjack/plugins/managers.py +22 -20
- crackerjack/py313.py +65 -21
- crackerjack/services/backup_service.py +467 -0
- crackerjack/services/bounded_status_operations.py +627 -0
- crackerjack/services/cache.py +7 -9
- crackerjack/services/config.py +35 -52
- crackerjack/services/config_integrity.py +5 -16
- crackerjack/services/config_merge.py +542 -0
- crackerjack/services/contextual_ai_assistant.py +17 -19
- crackerjack/services/coverage_ratchet.py +44 -73
- crackerjack/services/debug.py +25 -39
- crackerjack/services/dependency_monitor.py +52 -50
- crackerjack/services/enhanced_filesystem.py +14 -11
- crackerjack/services/file_hasher.py +1 -1
- crackerjack/services/filesystem.py +1 -12
- crackerjack/services/git.py +71 -47
- crackerjack/services/health_metrics.py +31 -27
- crackerjack/services/initialization.py +276 -428
- crackerjack/services/input_validator.py +760 -0
- crackerjack/services/log_manager.py +16 -16
- crackerjack/services/logging.py +7 -6
- crackerjack/services/metrics.py +43 -43
- crackerjack/services/pattern_cache.py +2 -31
- crackerjack/services/pattern_detector.py +26 -63
- crackerjack/services/performance_benchmarks.py +20 -45
- crackerjack/services/regex_patterns.py +2887 -0
- crackerjack/services/regex_utils.py +537 -0
- crackerjack/services/secure_path_utils.py +683 -0
- crackerjack/services/secure_status_formatter.py +534 -0
- crackerjack/services/secure_subprocess.py +605 -0
- crackerjack/services/security.py +47 -10
- crackerjack/services/security_logger.py +492 -0
- crackerjack/services/server_manager.py +109 -50
- crackerjack/services/smart_scheduling.py +8 -25
- crackerjack/services/status_authentication.py +603 -0
- crackerjack/services/status_security_manager.py +442 -0
- crackerjack/services/thread_safe_status_collector.py +546 -0
- crackerjack/services/tool_version_service.py +1 -23
- crackerjack/services/unified_config.py +36 -58
- crackerjack/services/validation_rate_limiter.py +269 -0
- crackerjack/services/version_checker.py +9 -40
- crackerjack/services/websocket_resource_limiter.py +572 -0
- crackerjack/slash_commands/__init__.py +52 -2
- crackerjack/tools/__init__.py +0 -0
- crackerjack/tools/validate_input_validator_patterns.py +262 -0
- crackerjack/tools/validate_regex_patterns.py +198 -0
- {crackerjack-0.31.10.dist-info → crackerjack-0.31.12.dist-info}/METADATA +197 -12
- crackerjack-0.31.12.dist-info/RECORD +178 -0
- crackerjack/cli/facade.py +0 -104
- crackerjack-0.31.10.dist-info/RECORD +0 -149
- {crackerjack-0.31.10.dist-info → crackerjack-0.31.12.dist-info}/WHEEL +0 -0
- {crackerjack-0.31.10.dist-info → crackerjack-0.31.12.dist-info}/entry_points.txt +0 -0
- {crackerjack-0.31.10.dist-info → crackerjack-0.31.12.dist-info}/licenses/LICENSE +0 -0
|
@@ -67,7 +67,6 @@ class DependencyMonitorService:
|
|
|
67
67
|
return False
|
|
68
68
|
|
|
69
69
|
def _parse_dependencies(self) -> dict[str, str]:
|
|
70
|
-
"""Parse dependencies from pyproject.toml file."""
|
|
71
70
|
try:
|
|
72
71
|
with self.pyproject_path.open("rb") as f:
|
|
73
72
|
data = tomllib.load(f)
|
|
@@ -82,7 +81,7 @@ class DependencyMonitorService:
|
|
|
82
81
|
|
|
83
82
|
except Exception as e:
|
|
84
83
|
self.console.print(
|
|
85
|
-
f"[yellow]Warning: Failed to parse pyproject.toml: {e}[/yellow]",
|
|
84
|
+
f"[yellow]Warning: Failed to parse pyproject.toml: {e}[/ yellow]",
|
|
86
85
|
)
|
|
87
86
|
return {}
|
|
88
87
|
|
|
@@ -91,7 +90,6 @@ class DependencyMonitorService:
|
|
|
91
90
|
project_data: dict[str, t.Any],
|
|
92
91
|
dependencies: dict[str, str],
|
|
93
92
|
) -> None:
|
|
94
|
-
"""Extract main dependencies from project data."""
|
|
95
93
|
if "dependencies" not in project_data:
|
|
96
94
|
return
|
|
97
95
|
|
|
@@ -105,7 +103,6 @@ class DependencyMonitorService:
|
|
|
105
103
|
project_data: dict[str, t.Any],
|
|
106
104
|
dependencies: dict[str, str],
|
|
107
105
|
) -> None:
|
|
108
|
-
"""Extract optional dependencies from project data."""
|
|
109
106
|
if "optional-dependencies" not in project_data:
|
|
110
107
|
return
|
|
111
108
|
|
|
@@ -119,7 +116,7 @@ class DependencyMonitorService:
|
|
|
119
116
|
if not spec or spec.startswith("-"):
|
|
120
117
|
return None, None
|
|
121
118
|
|
|
122
|
-
for operator in ("
|
|
119
|
+
for operator in ("> =", "< =", "= =", "~=", "! =", ">", "<"):
|
|
123
120
|
if operator in spec:
|
|
124
121
|
parts = spec.split(operator, 1)
|
|
125
122
|
if len(parts) == 2:
|
|
@@ -180,7 +177,6 @@ class DependencyMonitorService:
|
|
|
180
177
|
command_template: list[str],
|
|
181
178
|
parser_func: t.Callable[[t.Any], list[DependencyVulnerability]],
|
|
182
179
|
) -> list[DependencyVulnerability]:
|
|
183
|
-
"""Common logic for running vulnerability scanning tools."""
|
|
184
180
|
try:
|
|
185
181
|
temp_file = self._create_requirements_file(dependencies)
|
|
186
182
|
try:
|
|
@@ -197,14 +193,13 @@ class DependencyMonitorService:
|
|
|
197
193
|
json.JSONDecodeError,
|
|
198
194
|
Exception,
|
|
199
195
|
):
|
|
200
|
-
return []
|
|
196
|
+
return []
|
|
201
197
|
|
|
202
198
|
def _create_requirements_file(self, dependencies: dict[str, str]) -> str:
|
|
203
|
-
"""Create temporary requirements file for vulnerability scanning."""
|
|
204
199
|
with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False) as f:
|
|
205
200
|
for package, version in dependencies.items():
|
|
206
201
|
if version != "latest":
|
|
207
|
-
f.write(f"{package}
|
|
202
|
+
f.write(f"{package}= ={version}\n")
|
|
208
203
|
else:
|
|
209
204
|
f.write(f"{package}\n")
|
|
210
205
|
return f.name
|
|
@@ -214,7 +209,6 @@ class DependencyMonitorService:
|
|
|
214
209
|
command_template: list[str],
|
|
215
210
|
temp_file: str,
|
|
216
211
|
) -> subprocess.CompletedProcess[str]:
|
|
217
|
-
"""Execute vulnerability scanning command with temp file."""
|
|
218
212
|
cmd = [part.replace("__TEMP_FILE__", temp_file) for part in command_template]
|
|
219
213
|
return subprocess.run(
|
|
220
214
|
cmd,
|
|
@@ -229,7 +223,6 @@ class DependencyMonitorService:
|
|
|
229
223
|
result: subprocess.CompletedProcess[str],
|
|
230
224
|
parser_func: t.Callable[[t.Any], list[DependencyVulnerability]],
|
|
231
225
|
) -> list[DependencyVulnerability]:
|
|
232
|
-
"""Process vulnerability scan result using appropriate parser."""
|
|
233
226
|
if result.returncode == 0:
|
|
234
227
|
return []
|
|
235
228
|
|
|
@@ -309,10 +302,8 @@ class DependencyMonitorService:
|
|
|
309
302
|
cache: dict[str, t.Any],
|
|
310
303
|
current_time: float,
|
|
311
304
|
) -> MajorUpdate | None:
|
|
312
|
-
"""Check if a specific package has a major update available."""
|
|
313
305
|
cache_key = self._build_cache_key(package, current_version)
|
|
314
306
|
|
|
315
|
-
# Try to get from cache first
|
|
316
307
|
cached_update = self._get_cached_major_update(
|
|
317
308
|
cache_key,
|
|
318
309
|
cache,
|
|
@@ -323,7 +314,6 @@ class DependencyMonitorService:
|
|
|
323
314
|
if cached_update is not None:
|
|
324
315
|
return cached_update
|
|
325
316
|
|
|
326
|
-
# Check for updates and update cache
|
|
327
317
|
return self._fetch_and_cache_update_info(
|
|
328
318
|
package,
|
|
329
319
|
current_version,
|
|
@@ -333,7 +323,6 @@ class DependencyMonitorService:
|
|
|
333
323
|
)
|
|
334
324
|
|
|
335
325
|
def _build_cache_key(self, package: str, current_version: str) -> str:
|
|
336
|
-
"""Build cache key for package version."""
|
|
337
326
|
return f"{package}_{current_version}"
|
|
338
327
|
|
|
339
328
|
def _get_cached_major_update(
|
|
@@ -344,7 +333,6 @@ class DependencyMonitorService:
|
|
|
344
333
|
package: str,
|
|
345
334
|
current_version: str,
|
|
346
335
|
) -> MajorUpdate | None:
|
|
347
|
-
"""Get major update from cache if available and valid."""
|
|
348
336
|
if not self._is_cache_entry_valid(cache_key, cache, current_time):
|
|
349
337
|
return None
|
|
350
338
|
|
|
@@ -364,13 +352,12 @@ class DependencyMonitorService:
|
|
|
364
352
|
cache: dict[str, t.Any],
|
|
365
353
|
current_time: float,
|
|
366
354
|
) -> bool:
|
|
367
|
-
"""Check if cache entry exists and is not expired."""
|
|
368
355
|
if cache_key not in cache:
|
|
369
356
|
return False
|
|
370
357
|
|
|
371
358
|
cached_data = cache[cache_key]
|
|
372
359
|
cache_age = current_time - cached_data["timestamp"]
|
|
373
|
-
return cache_age < 86400
|
|
360
|
+
return cache_age < 86400
|
|
374
361
|
|
|
375
362
|
def _create_major_update_from_cache(
|
|
376
363
|
self,
|
|
@@ -378,7 +365,6 @@ class DependencyMonitorService:
|
|
|
378
365
|
current_version: str,
|
|
379
366
|
cached_data: dict[str, t.Any],
|
|
380
367
|
) -> MajorUpdate:
|
|
381
|
-
"""Create MajorUpdate instance from cached data."""
|
|
382
368
|
return MajorUpdate(
|
|
383
369
|
package=package,
|
|
384
370
|
current_version=current_version,
|
|
@@ -395,7 +381,6 @@ class DependencyMonitorService:
|
|
|
395
381
|
cache: dict[str, t.Any],
|
|
396
382
|
current_time: float,
|
|
397
383
|
) -> MajorUpdate | None:
|
|
398
|
-
"""Fetch latest version info and cache the result."""
|
|
399
384
|
latest_info = self._get_latest_version_info(package)
|
|
400
385
|
if not latest_info:
|
|
401
386
|
return None
|
|
@@ -427,7 +412,6 @@ class DependencyMonitorService:
|
|
|
427
412
|
latest_info: dict[str, t.Any],
|
|
428
413
|
has_major_update: bool,
|
|
429
414
|
) -> MajorUpdate | None:
|
|
430
|
-
"""Create MajorUpdate instance if there is a major update available."""
|
|
431
415
|
if not has_major_update:
|
|
432
416
|
return None
|
|
433
417
|
|
|
@@ -447,7 +431,6 @@ class DependencyMonitorService:
|
|
|
447
431
|
has_major_update: bool,
|
|
448
432
|
latest_info: dict[str, t.Any],
|
|
449
433
|
) -> None:
|
|
450
|
-
"""Update cache with latest version information."""
|
|
451
434
|
cache[cache_key] = {
|
|
452
435
|
"timestamp": current_time,
|
|
453
436
|
"has_major_update": has_major_update,
|
|
@@ -464,23 +447,40 @@ class DependencyMonitorService:
|
|
|
464
447
|
return None
|
|
465
448
|
|
|
466
449
|
def _fetch_pypi_data(self, package: str) -> dict[str, t.Any]:
|
|
467
|
-
"""Fetch package data from PyPI API."""
|
|
468
450
|
import urllib.request
|
|
451
|
+
from urllib.parse import urlparse
|
|
469
452
|
|
|
470
453
|
url = f"https://pypi.org/pypi/{package}/json"
|
|
471
454
|
self._validate_pypi_url(url)
|
|
472
455
|
|
|
456
|
+
parsed = urlparse(url)
|
|
457
|
+
# Restrict to https scheme only for security (B310)
|
|
458
|
+
if parsed.scheme != "https" or parsed.netloc != "pypi.org":
|
|
459
|
+
msg = f"Invalid URL: only https://pypi.org URLs are allowed, got {url}"
|
|
460
|
+
raise ValueError(msg)
|
|
461
|
+
|
|
462
|
+
# B310: Safe urllib.urlopen with scheme validation
|
|
473
463
|
with urllib.request.urlopen(url, timeout=10) as response: # nosec B310
|
|
474
464
|
return json.load(response)
|
|
475
465
|
|
|
476
466
|
def _validate_pypi_url(self, url: str) -> None:
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
467
|
+
from urllib.parse import urlparse
|
|
468
|
+
|
|
469
|
+
parsed = urlparse(url)
|
|
470
|
+
|
|
471
|
+
if parsed.scheme != "https":
|
|
472
|
+
msg = f"Invalid URL scheme '{parsed.scheme}': only HTTPS is allowed"
|
|
473
|
+
raise ValueError(msg)
|
|
474
|
+
|
|
475
|
+
if parsed.netloc != "pypi.org":
|
|
476
|
+
msg = f"Invalid hostname '{parsed.netloc}': only pypi.org is allowed"
|
|
477
|
+
raise ValueError(msg)
|
|
478
|
+
|
|
479
|
+
if not parsed.path.startswith("/pypi/") or not parsed.path.endswith("/json"):
|
|
480
|
+
msg = f"Invalid PyPI API path: {parsed.path}"
|
|
480
481
|
raise ValueError(msg)
|
|
481
482
|
|
|
482
483
|
def _extract_version_info(self, data: dict[str, t.Any]) -> dict[str, t.Any] | None:
|
|
483
|
-
"""Extract version information from PyPI response data."""
|
|
484
484
|
info = data.get("info", {})
|
|
485
485
|
releases = data.get("releases", {})
|
|
486
486
|
|
|
@@ -498,14 +498,12 @@ class DependencyMonitorService:
|
|
|
498
498
|
}
|
|
499
499
|
|
|
500
500
|
def _get_release_date(self, releases: dict[str, t.Any], version: str) -> str:
|
|
501
|
-
"""Extract release date for a specific version."""
|
|
502
501
|
release_info = releases.get(version, [])
|
|
503
502
|
if release_info:
|
|
504
503
|
return release_info[0].get("upload_time", "")
|
|
505
504
|
return ""
|
|
506
505
|
|
|
507
506
|
def _has_breaking_changes(self, version: str) -> bool:
|
|
508
|
-
"""Determine if version likely has breaking changes based on major version."""
|
|
509
507
|
return version.split(".")[0] != "0" if "." in version else False
|
|
510
508
|
|
|
511
509
|
def _is_major_version_update(self, current: str, latest: str) -> bool:
|
|
@@ -549,69 +547,73 @@ class DependencyMonitorService:
|
|
|
549
547
|
self,
|
|
550
548
|
vulnerabilities: list[DependencyVulnerability],
|
|
551
549
|
) -> None:
|
|
552
|
-
self.console.print("\n[bold red]🚨 Security Vulnerabilities Found![/bold red]")
|
|
553
550
|
self.console.print(
|
|
554
|
-
"[red]
|
|
551
|
+
"\n[bold red]🚨 Security Vulnerabilities Found ![/ bold red]"
|
|
552
|
+
)
|
|
553
|
+
self.console.print(
|
|
554
|
+
"[red]Please update the following packages immediately: [/ red]\n",
|
|
555
555
|
)
|
|
556
556
|
|
|
557
557
|
for vuln in vulnerabilities:
|
|
558
|
-
self.console.print(f"[red]• {vuln.package} {vuln.installed_version}[/red]")
|
|
559
|
-
self.console.print(
|
|
560
|
-
|
|
558
|
+
self.console.print(f"[red]• {vuln.package} {vuln.installed_version}[/ red]")
|
|
559
|
+
self.console.print(
|
|
560
|
+
f" [dim]Vulnerability ID: {vuln.vulnerability_id}[/ dim]"
|
|
561
|
+
)
|
|
562
|
+
self.console.print(f" [dim]Severity: {vuln.severity.upper()}[/ dim]")
|
|
561
563
|
if vuln.patched_version:
|
|
562
564
|
self.console.print(
|
|
563
|
-
f" [green]Fix available: {vuln.patched_version}[/green]",
|
|
565
|
+
f" [green]Fix available: {vuln.patched_version}[/ green]",
|
|
564
566
|
)
|
|
565
567
|
if vuln.advisory_url:
|
|
566
|
-
self.console.print(f" [dim]More info: {vuln.advisory_url}[/dim]")
|
|
568
|
+
self.console.print(f" [dim]More info: {vuln.advisory_url}[/ dim]")
|
|
567
569
|
self.console.print()
|
|
568
570
|
|
|
569
571
|
def _report_major_updates(self, major_updates: list[MajorUpdate]) -> None:
|
|
570
572
|
self.console.print(
|
|
571
|
-
"\n[bold yellow]📦 Major Version Updates Available[/bold yellow]",
|
|
573
|
+
"\n[bold yellow]📦 Major Version Updates Available[/ bold yellow]",
|
|
572
574
|
)
|
|
573
575
|
self.console.print(
|
|
574
|
-
"[yellow]The following packages have major updates:[/yellow]\n",
|
|
576
|
+
"[yellow]The following packages have major updates: [/ yellow]\n",
|
|
575
577
|
)
|
|
576
578
|
|
|
577
579
|
for update in major_updates:
|
|
578
|
-
self.console.print(f"[yellow]• {update.package}[/yellow]")
|
|
579
|
-
self.console.print(f" [dim]Current: {update.current_version}[/dim]")
|
|
580
|
-
self.console.print(f" [dim]Latest: {update.latest_version}[/dim]")
|
|
580
|
+
self.console.print(f"[yellow]• {update.package}[/ yellow]")
|
|
581
|
+
self.console.print(f" [dim]Current: {update.current_version}[/ dim]")
|
|
582
|
+
self.console.print(f" [dim]Latest: {update.latest_version}[/ dim]")
|
|
581
583
|
if update.release_date:
|
|
582
584
|
release_date = update.release_date[:10]
|
|
583
|
-
self.console.print(f" [dim]Released: {release_date}[/dim]")
|
|
585
|
+
self.console.print(f" [dim]Released: {release_date}[/ dim]")
|
|
584
586
|
if update.breaking_changes:
|
|
585
|
-
self.console.print(" [red]⚠️ May contain breaking changes[/red]")
|
|
587
|
+
self.console.print(" [red]⚠️ May contain breaking changes[/ red]")
|
|
586
588
|
self.console.print()
|
|
587
589
|
|
|
588
590
|
self.console.print(
|
|
589
|
-
"[dim]Review changelogs before updating to major versions.[/dim]",
|
|
591
|
+
"[dim]Review changelogs before updating to major versions.[/ dim]",
|
|
590
592
|
)
|
|
591
593
|
|
|
592
594
|
def force_check_updates(
|
|
593
595
|
self,
|
|
594
596
|
) -> tuple[list[DependencyVulnerability], list[MajorUpdate]]:
|
|
595
597
|
if not self.pyproject_path.exists():
|
|
596
|
-
self.console.print("[yellow]⚠️ No pyproject.toml found[/yellow]")
|
|
598
|
+
self.console.print("[yellow]⚠️ No pyproject.toml found[/ yellow]")
|
|
597
599
|
return [], []
|
|
598
600
|
|
|
599
|
-
self.console.print("[dim]Parsing dependencies from pyproject.toml...[/dim]")
|
|
601
|
+
self.console.print("[dim]Parsing dependencies from pyproject.toml...[/ dim]")
|
|
600
602
|
dependencies = self._parse_dependencies()
|
|
601
603
|
if not dependencies:
|
|
602
604
|
self.console.print(
|
|
603
|
-
"[yellow]⚠️ No dependencies found in pyproject.toml[/yellow]",
|
|
605
|
+
"[yellow]⚠️ No dependencies found in pyproject.toml[/ yellow]",
|
|
604
606
|
)
|
|
605
607
|
return [], []
|
|
606
608
|
|
|
607
609
|
self.console.print(
|
|
608
|
-
f"[dim]Found {len(dependencies)} dependencies to check[/dim]",
|
|
610
|
+
f"[dim]Found {len(dependencies)} dependencies to check[/ dim]",
|
|
609
611
|
)
|
|
610
612
|
|
|
611
|
-
self.console.print("[dim]Checking for security vulnerabilities...[/dim]")
|
|
613
|
+
self.console.print("[dim]Checking for security vulnerabilities...[/ dim]")
|
|
612
614
|
vulnerabilities = self._check_security_vulnerabilities(dependencies)
|
|
613
615
|
|
|
614
|
-
self.console.print("[dim]Checking for major version updates...[/dim]")
|
|
616
|
+
self.console.print("[dim]Checking for major version updates...[/ dim]")
|
|
615
617
|
major_updates = self._check_major_updates(dependencies)
|
|
616
618
|
|
|
617
619
|
return vulnerabilities, major_updates
|
|
@@ -136,7 +136,8 @@ class BatchFileOperations:
|
|
|
136
136
|
|
|
137
137
|
await asyncio.gather(*tasks, return_exceptions=True)
|
|
138
138
|
|
|
139
|
-
|
|
139
|
+
@staticmethod
|
|
140
|
+
async def _read_single_async(path: Path, future: asyncio.Future[str]) -> None:
|
|
140
141
|
try:
|
|
141
142
|
async with aiofiles.open(path, encoding="utf-8") as f:
|
|
142
143
|
content = await f.read()
|
|
@@ -144,8 +145,8 @@ class BatchFileOperations:
|
|
|
144
145
|
except Exception as e:
|
|
145
146
|
future.set_exception(e)
|
|
146
147
|
|
|
148
|
+
@staticmethod
|
|
147
149
|
async def _write_single_async(
|
|
148
|
-
self,
|
|
149
150
|
path: Path,
|
|
150
151
|
content: str,
|
|
151
152
|
future: asyncio.Future[None],
|
|
@@ -192,9 +193,6 @@ class EnhancedFileSystemService(FileSystemInterface):
|
|
|
192
193
|
return content
|
|
193
194
|
|
|
194
195
|
def write_file(self, path: str | Path, content: str) -> None:
|
|
195
|
-
if not isinstance(content, str): # type: ignore[arg-type]
|
|
196
|
-
raise TypeError("Content must be a string")
|
|
197
|
-
|
|
198
196
|
path_obj = Path(path) if isinstance(path, str) else path
|
|
199
197
|
|
|
200
198
|
with LoggingContext("write_file", path=str(path_obj), size=len(content)):
|
|
@@ -286,7 +284,8 @@ class EnhancedFileSystemService(FileSystemInterface):
|
|
|
286
284
|
|
|
287
285
|
await asyncio.gather(*tasks, return_exceptions=True)
|
|
288
286
|
|
|
289
|
-
|
|
287
|
+
@staticmethod
|
|
288
|
+
def _get_cache_key(path: Path) -> str:
|
|
290
289
|
path_str = str(path.resolve())
|
|
291
290
|
return hashlib.md5(path_str.encode(), usedforsecurity=False).hexdigest()
|
|
292
291
|
|
|
@@ -306,7 +305,8 @@ class EnhancedFileSystemService(FileSystemInterface):
|
|
|
306
305
|
|
|
307
306
|
return self.cache.get(cache_key)
|
|
308
307
|
|
|
309
|
-
|
|
308
|
+
@staticmethod
|
|
309
|
+
def _read_file_direct(path: Path) -> str:
|
|
310
310
|
try:
|
|
311
311
|
if not path.exists():
|
|
312
312
|
raise FileError(
|
|
@@ -325,7 +325,7 @@ class EnhancedFileSystemService(FileSystemInterface):
|
|
|
325
325
|
raise FileError(
|
|
326
326
|
message=f"Unable to decode file as UTF-8: {path}",
|
|
327
327
|
details=str(e),
|
|
328
|
-
recovery="Ensure file is text-based and UTF-8 encoded",
|
|
328
|
+
recovery="Ensure file is text - based and UTF-8 encoded",
|
|
329
329
|
) from e
|
|
330
330
|
except OSError as e:
|
|
331
331
|
raise FileError(
|
|
@@ -334,7 +334,8 @@ class EnhancedFileSystemService(FileSystemInterface):
|
|
|
334
334
|
recovery="Check disk space and file system integrity",
|
|
335
335
|
) from e
|
|
336
336
|
|
|
337
|
-
|
|
337
|
+
@staticmethod
|
|
338
|
+
def _write_file_direct(path: Path, content: str) -> None:
|
|
338
339
|
try:
|
|
339
340
|
try:
|
|
340
341
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
@@ -360,7 +361,8 @@ class EnhancedFileSystemService(FileSystemInterface):
|
|
|
360
361
|
recovery="Check disk space and file system integrity",
|
|
361
362
|
) from e
|
|
362
363
|
|
|
363
|
-
|
|
364
|
+
@staticmethod
|
|
365
|
+
def file_exists(path: str | Path) -> bool:
|
|
364
366
|
return (Path(path) if isinstance(path, str) else path).exists()
|
|
365
367
|
|
|
366
368
|
def create_directory(self, path: str | Path) -> None:
|
|
@@ -394,7 +396,8 @@ class EnhancedFileSystemService(FileSystemInterface):
|
|
|
394
396
|
recovery="Check file permissions",
|
|
395
397
|
) from e
|
|
396
398
|
|
|
397
|
-
|
|
399
|
+
@staticmethod
|
|
400
|
+
def list_files(path: str | Path, pattern: str = "*") -> Iterator[Path]:
|
|
398
401
|
path_obj = Path(path) if isinstance(path, str) else path
|
|
399
402
|
|
|
400
403
|
if not path_obj.is_dir():
|
|
@@ -110,7 +110,7 @@ class FileHasher:
|
|
|
110
110
|
if file_path:
|
|
111
111
|
stat = file_path.stat() if file_path.exists() else None
|
|
112
112
|
if stat:
|
|
113
|
-
cache_key = f"file_hash:{file_path}:{stat.st_mtime}:{stat.st_size}"
|
|
113
|
+
cache_key = f"file_hash: {file_path}: {stat.st_mtime}: {stat.st_size}"
|
|
114
114
|
self.cache.file_hash_cache.invalidate(cache_key)
|
|
115
115
|
else:
|
|
116
116
|
self.cache.file_hash_cache.clear()
|
|
@@ -8,19 +8,9 @@ from crackerjack.errors import ErrorCode, FileError, ResourceError
|
|
|
8
8
|
class FileSystemService:
|
|
9
9
|
@staticmethod
|
|
10
10
|
def clean_trailing_whitespace_and_newlines(content: str) -> str:
|
|
11
|
-
"""Clean trailing whitespace from all lines and ensure single trailing newline.
|
|
12
|
-
|
|
13
|
-
Args:
|
|
14
|
-
content: File content to clean
|
|
15
|
-
|
|
16
|
-
Returns:
|
|
17
|
-
Cleaned content with no trailing whitespace and single trailing newline
|
|
18
|
-
"""
|
|
19
|
-
# Remove trailing whitespace from each line
|
|
20
11
|
lines = content.splitlines()
|
|
21
12
|
cleaned_lines = [line.rstrip() for line in lines]
|
|
22
13
|
|
|
23
|
-
# Join lines and ensure exactly one trailing newline
|
|
24
14
|
result = "\n".join(cleaned_lines)
|
|
25
15
|
if result and not result.endswith("\n"):
|
|
26
16
|
result += "\n"
|
|
@@ -49,7 +39,7 @@ class FileSystemService:
|
|
|
49
39
|
message=f"Unable to decode file as UTF-8: {path}",
|
|
50
40
|
error_code=ErrorCode.FILE_READ_ERROR,
|
|
51
41
|
details=str(e),
|
|
52
|
-
recovery="Ensure file is text-based and UTF-8 encoded",
|
|
42
|
+
recovery="Ensure file is text - based and UTF-8 encoded",
|
|
53
43
|
) from e
|
|
54
44
|
except OSError as e:
|
|
55
45
|
raise FileError(
|
|
@@ -72,7 +62,6 @@ class FileSystemService:
|
|
|
72
62
|
recovery="Check disk space and directory permissions",
|
|
73
63
|
) from e
|
|
74
64
|
|
|
75
|
-
# Auto-clean configuration files to prevent pre-commit hook failures
|
|
76
65
|
if path_obj.name in {".pre-commit-config.yaml", "pyproject.toml"}:
|
|
77
66
|
content = self.clean_trailing_whitespace_and_newlines(content)
|
|
78
67
|
|