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.

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 +281 -94
  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 +343 -209
  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 +17 -63
  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 +44 -73
  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 +71 -47
  119. crackerjack/services/health_metrics.py +31 -27
  120. crackerjack/services/initialization.py +276 -428
  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.10.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.10.dist-info/RECORD +0 -149
  153. {crackerjack-0.31.10.dist-info → crackerjack-0.31.12.dist-info}/WHEEL +0 -0
  154. {crackerjack-0.31.10.dist-info → crackerjack-0.31.12.dist-info}/entry_points.txt +0 -0
  155. {crackerjack-0.31.10.dist-info → crackerjack-0.31.12.dist-info}/licenses/LICENSE +0 -0
@@ -1,17 +1,9 @@
1
- """Test progress tracking and display functionality.
2
-
3
- This module handles real-time test execution progress tracking, including collection
4
- and execution phases. Split from test_manager.py for better separation of concerns.
5
- """
6
-
7
1
  import threading
8
2
  import time
9
3
  import typing as t
10
4
 
11
5
 
12
6
  class TestProgress:
13
- """Tracks test execution progress with thread-safe updates."""
14
-
15
7
  def __init__(self) -> None:
16
8
  self.total_tests: int = 0
17
9
  self.passed: int = 0
@@ -25,21 +17,18 @@ class TestProgress:
25
17
  self.files_discovered: int = 0
26
18
  self.collection_status: str = "Starting collection..."
27
19
  self._lock = threading.Lock()
28
- self._seen_files: set[str] = set() # Track seen files to prevent duplicates
20
+ self._seen_files: set[str] = set()
29
21
 
30
22
  @property
31
23
  def completed(self) -> int:
32
- """Total completed tests (passed + failed + skipped + errors)."""
33
24
  return self.passed + self.failed + self.skipped + self.errors
34
25
 
35
26
  @property
36
27
  def elapsed_time(self) -> float:
37
- """Elapsed time since test start."""
38
28
  return time.time() - self.start_time if self.start_time else 0
39
29
 
40
30
  @property
41
31
  def eta_seconds(self) -> float | None:
42
- """Estimated time to completion based on current progress rate."""
43
32
  if self.completed <= 0 or self.total_tests <= 0:
44
33
  return None
45
34
  progress_rate = (
@@ -49,20 +38,17 @@ class TestProgress:
49
38
  return remaining / progress_rate if progress_rate > 0 else None
50
39
 
51
40
  def update(self, **kwargs: t.Any) -> None:
52
- """Thread-safe update of progress attributes."""
53
41
  with self._lock:
54
42
  for key, value in kwargs.items():
55
43
  if hasattr(self, key):
56
44
  setattr(self, key, value)
57
45
 
58
46
  def format_progress(self) -> str:
59
- """Format progress display for Rich output."""
60
47
  if self.is_collecting:
61
48
  return self._format_collection_progress()
62
49
  return self._format_execution_progress()
63
50
 
64
51
  def _format_collection_progress(self) -> str:
65
- """Format test collection progress display."""
66
52
  status_parts = [self.collection_status]
67
53
 
68
54
  if self.files_discovered > 0:
@@ -70,20 +56,17 @@ class TestProgress:
70
56
 
71
57
  elapsed = self.elapsed_time
72
58
  if elapsed > 1:
73
- status_parts.append(f"{elapsed:.1f}s")
59
+ status_parts.append(f"{elapsed: .1f}s")
74
60
 
75
61
  return " | ".join(status_parts)
76
62
 
77
63
  def _format_execution_progress(self) -> str:
78
- """Format test execution progress display."""
79
64
  parts = []
80
65
 
81
- # Test progress
82
66
  if self.total_tests > 0:
83
67
  progress_pct = (self.completed / self.total_tests) * 100
84
- parts.append(f"{self.completed}/{self.total_tests} ({progress_pct:.1f}%)")
68
+ parts.append(f"{self.completed}/{self.total_tests} ({progress_pct: .1f}%)")
85
69
 
86
- # Status counts
87
70
  status_parts = []
88
71
  if self.passed > 0:
89
72
  status_parts.append(f"✅ {self.passed}")
@@ -97,7 +80,6 @@ class TestProgress:
97
80
  if status_parts:
98
81
  parts.append(" ".join(status_parts))
99
82
 
100
- # Current test (truncated)
101
83
  if self.current_test and not self.is_complete:
102
84
  test_name = (
103
85
  self.current_test[:30] + "..."
@@ -106,9 +88,8 @@ class TestProgress:
106
88
  )
107
89
  parts.append(f"Running: {test_name}")
108
90
 
109
- # Timing
110
91
  elapsed = self.elapsed_time
111
92
  if elapsed > 1:
112
- parts.append(f"{elapsed:.1f}s")
93
+ parts.append(f"{elapsed: .1f}s")
113
94
 
114
95
  return " | ".join(parts)
crackerjack/mcp/cache.py CHANGED
@@ -2,6 +2,7 @@ import asyncio
2
2
  import json
3
3
  import time
4
4
  import typing as t
5
+ from contextlib import suppress
5
6
  from dataclasses import asdict, dataclass
6
7
  from pathlib import Path
7
8
 
@@ -188,8 +189,8 @@ class ErrorCache:
188
189
  def _extract_pyright_info(self, line: str) -> tuple[str, str]:
189
190
  error_code = ""
190
191
  message_pattern = line
191
- if " - error: " in line:
192
- parts = line.split(" - error: ")
192
+ if "-error: " in line:
193
+ parts = line.split("-error: ")
193
194
  if len(parts) >= 2:
194
195
  message_pattern = parts[1].strip()
195
196
  if "(" in message_pattern and ")" in message_pattern:
@@ -314,23 +315,15 @@ class ErrorCache:
314
315
  self.fix_results = []
315
316
 
316
317
  def _save_patterns(self) -> None:
317
- try:
318
+ with suppress(OSError, json.JSONEncodeError):
318
319
  patterns_data = {
319
320
  pid: pattern.to_dict() for pid, pattern in self.patterns.items()
320
321
  }
321
322
  with self.patterns_file.open("w") as f:
322
323
  json.dump(patterns_data, f, indent=2)
323
- except (OSError, json.JSONEncodeError):
324
- pass
325
- except Exception:
326
- pass
327
324
 
328
325
  def _save_fixes(self) -> None:
329
- try:
326
+ with suppress(OSError, json.JSONEncodeError):
330
327
  fixes_data = [result.to_dict() for result in self.fix_results]
331
328
  with self.fixes_file.open("w") as f:
332
329
  json.dump(fixes_data, f, indent=2)
333
- except (OSError, json.JSONEncodeError):
334
- pass
335
- except Exception:
336
- pass
@@ -26,10 +26,10 @@ async def ensure_mcp_server_running() -> subprocess.Popen | None:
26
26
  console = Console()
27
27
 
28
28
  if is_mcp_server_running():
29
- console.print("[green]✅ MCP server already running[/green]")
29
+ console.print("[green]✅ MCP server already running[/ green]")
30
30
  return None
31
31
 
32
- console.print("[yellow]🚀 Starting MCP server...[/yellow]")
32
+ console.print("[yellow]🚀 Starting MCP server...[/ yellow]")
33
33
  server_process = subprocess.Popen(
34
34
  [sys.executable, "-m", "crackerjack", "--start-mcp-server"],
35
35
  stdout=subprocess.PIPE,
@@ -39,17 +39,17 @@ async def ensure_mcp_server_running() -> subprocess.Popen | None:
39
39
 
40
40
  for _i in range(20):
41
41
  if is_mcp_server_running():
42
- console.print("[green]✅ MCP server started successfully[/green]")
42
+ console.print("[green]✅ MCP server started successfully[/ green]")
43
43
  return server_process
44
44
  await asyncio.sleep(0.5)
45
45
 
46
- console.print("[red]❌ Failed to start MCP server[/red]")
46
+ console.print("[red]❌ Failed to start MCP server[/ red]")
47
47
  server_process.terminate()
48
48
  msg = "Failed to start MCP server within timeout period"
49
49
  raise RuntimeError(msg)
50
50
 
51
51
 
52
- async def run_with_mcp_server(command: str = "/crackerjack:run") -> None:
52
+ async def run_with_mcp_server(command: str = "/ crackerjack: run") -> None:
53
53
  console = Console()
54
54
 
55
55
  server_process = await ensure_mcp_server_running()
@@ -70,12 +70,12 @@ async def run_with_mcp_server(command: str = "/crackerjack:run") -> None:
70
70
  try:
71
71
  await run_crackerjack_with_progress(session, command)
72
72
  except Exception as e:
73
- console.print(f"[bold red]Error: {e}[/bold red]")
73
+ console.print(f"[bold red]Error: {e}[/ bold red]")
74
74
  sys.exit(1)
75
75
  finally:
76
76
  if server_process:
77
77
  console.print(
78
- "[yellow]Note: MCP server continues running in background[/yellow]",
78
+ "[yellow]Note: MCP server continues running in background[/ yellow]",
79
79
  )
80
80
 
81
81
 
@@ -88,15 +88,15 @@ def main() -> None:
88
88
  parser.add_argument(
89
89
  "command",
90
90
  nargs="?",
91
- default="/crackerjack:run",
92
- help="Command to execute (default: /crackerjack:run)",
91
+ default="/ crackerjack: run",
92
+ help="Command to execute (default: / crackerjack: run)",
93
93
  )
94
94
  args = parser.parse_args()
95
95
 
96
96
  try:
97
97
  asyncio.run(run_with_mcp_server(args.command))
98
98
  except KeyboardInterrupt:
99
- Console().print("\n[yellow]Operation cancelled by user[/yellow]")
99
+ Console().print("\n[yellow]Operation cancelled by user[/ yellow]")
100
100
  sys.exit(1)
101
101
 
102
102
 
@@ -12,7 +12,13 @@ from types import TracebackType
12
12
 
13
13
  from rich.console import Console
14
14
 
15
+ from crackerjack.core.resource_manager import (
16
+ ResourceManager,
17
+ register_global_resource_manager,
18
+ )
19
+ from crackerjack.core.websocket_lifecycle import NetworkResourceManager
15
20
  from crackerjack.core.workflow_orchestrator import WorkflowOrchestrator
21
+ from crackerjack.services.secure_path_utils import SecurePathValidator
16
22
 
17
23
  from .cache import ErrorCache
18
24
  from .rate_limiter import RateLimitConfig, RateLimitMiddleware
@@ -123,11 +129,31 @@ class MCPServerConfig:
123
129
  state_dir: Path | None = None
124
130
  cache_dir: Path | None = None
125
131
 
132
+ def __post_init__(self) -> None:
133
+ # Validate all paths using secure path validation
134
+ self.project_path = SecurePathValidator.validate_safe_path(self.project_path)
135
+
136
+ if self.progress_dir:
137
+ self.progress_dir = SecurePathValidator.validate_safe_path(
138
+ self.progress_dir
139
+ )
140
+
141
+ if self.state_dir:
142
+ self.state_dir = SecurePathValidator.validate_safe_path(self.state_dir)
143
+
144
+ if self.cache_dir:
145
+ self.cache_dir = SecurePathValidator.validate_safe_path(self.cache_dir)
146
+
126
147
 
127
148
  class MCPServerContext:
128
149
  def __init__(self, config: MCPServerConfig) -> None:
129
150
  self.config = config
130
151
 
152
+ # Resource management
153
+ self.resource_manager = ResourceManager()
154
+ self.network_manager = NetworkResourceManager()
155
+ register_global_resource_manager(self.resource_manager)
156
+
131
157
  self.console: Console | None = None
132
158
  self.cli_runner = None
133
159
  self.state_manager: StateManager | None = None
@@ -202,6 +228,7 @@ class MCPServerContext:
202
228
  if not self._initialized:
203
229
  return
204
230
 
231
+ # Run custom shutdown tasks first
205
232
  for task in reversed(self._shutdown_tasks):
206
233
  try:
207
234
  await task()
@@ -209,19 +236,40 @@ class MCPServerContext:
209
236
  if self.console:
210
237
  self.console.print(f"[red]Error during shutdown: {e}[/red]")
211
238
 
239
+ # Cancel health check task
212
240
  if self._websocket_health_check_task:
213
241
  self._websocket_health_check_task.cancel()
214
242
  with contextlib.suppress(asyncio.CancelledError):
215
243
  await self._websocket_health_check_task
216
244
  self._websocket_health_check_task = None
217
245
 
246
+ # Stop WebSocket server
218
247
  await self._stop_websocket_server()
219
248
 
249
+ # Stop rate limiter
220
250
  if self.rate_limiter:
221
251
  await self.rate_limiter.stop()
222
252
 
253
+ # Stop batched saver
223
254
  await self.batched_saver.stop()
224
255
 
256
+ # Clean up all managed resources
257
+ try:
258
+ await self.network_manager.cleanup_all()
259
+ except Exception as e:
260
+ if self.console:
261
+ self.console.print(
262
+ f"[yellow]Warning: Network resource cleanup error: {e}[/yellow]"
263
+ )
264
+
265
+ try:
266
+ await self.resource_manager.cleanup_all()
267
+ except Exception as e:
268
+ if self.console:
269
+ self.console.print(
270
+ f"[yellow]Warning: Resource cleanup error: {e}[/yellow]"
271
+ )
272
+
225
273
  self._initialized = False
226
274
 
227
275
  def add_startup_task(self, task: t.Callable[[], t.Awaitable[None]]) -> None:
@@ -241,9 +289,9 @@ class MCPServerContext:
241
289
  uuid.UUID(job_id)
242
290
  return True
243
291
 
244
- import re
292
+ from crackerjack.services.regex_patterns import is_valid_job_id
245
293
 
246
- if not re.match(r"^[a-zA-Z0-9_-]+$", job_id):
294
+ if not is_valid_job_id(job_id):
247
295
  return False
248
296
 
249
297
  if ".." in job_id or "/" in job_id or "\\" in job_id:
@@ -268,7 +316,7 @@ class MCPServerContext:
268
316
 
269
317
  if self.console:
270
318
  self.console.print(
271
- f"🚀 Starting WebSocket server on localhost:{self.websocket_server_port}...",
319
+ f"🚀 Starting WebSocket server on localhost: {self.websocket_server_port}...",
272
320
  )
273
321
 
274
322
  try:
@@ -321,6 +369,13 @@ class MCPServerContext:
321
369
  start_new_session=True,
322
370
  )
323
371
 
372
+ # Register the process with the network resource manager for automatic cleanup
373
+ if self.websocket_server_process:
374
+ managed_process = self.network_manager.create_subprocess(
375
+ self.websocket_server_process, timeout=30.0
376
+ )
377
+ await managed_process.start_monitoring()
378
+
324
379
  async def _register_websocket_cleanup(self) -> None:
325
380
  if not self._websocket_cleanup_registered:
326
381
  self.add_shutdown_task(self._stop_websocket_server)
@@ -351,7 +406,7 @@ class MCPServerContext:
351
406
  f"✅ WebSocket server started successfully on port {self.websocket_server_port}",
352
407
  )
353
408
  self.console.print(
354
- f"📊 Progress available at: ws://localhost:{self.websocket_server_port}/ws/progress/{{job_id}}",
409
+ f"📊 Progress available at: ws: / / localhost: {self.websocket_server_port}/ ws / progress /{{job_id}}",
355
410
  )
356
411
  return True
357
412
 
@@ -512,7 +567,11 @@ class MCPServerContext:
512
567
  if not self.validate_job_id(job_id):
513
568
  msg = f"Invalid job_id: {job_id}"
514
569
  raise ValueError(msg)
515
- return self.progress_dir / f"job-{job_id}.json"
570
+
571
+ # Use secure path joining to prevent directory traversal
572
+ return SecurePathValidator.secure_path_join(
573
+ self.progress_dir, f"job-{job_id}.json"
574
+ )
516
575
 
517
576
  async def schedule_state_save(
518
577
  self,
@@ -522,7 +581,6 @@ class MCPServerContext:
522
581
  await self.batched_saver.schedule_save(save_id, save_func)
523
582
 
524
583
  def get_current_time(self) -> str:
525
- """Get current timestamp as string for progress tracking."""
526
584
  import datetime
527
585
 
528
586
  return datetime.datetime.now().isoformat()
@@ -63,7 +63,7 @@ class MetricCard(Static):
63
63
  }
64
64
  """
65
65
 
66
- value = reactive(" -- ")
66
+ value = reactive(" --")
67
67
  label = reactive("Metric")
68
68
  trend = reactive("")
69
69
  status = reactive("")
@@ -71,7 +71,7 @@ class MetricCard(Static):
71
71
  def __init__(
72
72
  self,
73
73
  label: str,
74
- value: str = " -- ",
74
+ value: str = " --",
75
75
  trend: str = "",
76
76
  status: str = "",
77
77
  **kwargs,
@@ -414,7 +414,7 @@ class CrackerjackDashboard(App):
414
414
  try:
415
415
  timeout = aiohttp.ClientTimeout(total=5.0)
416
416
  async with aiohttp.ClientSession(timeout=timeout) as session:
417
- async with session.get("http://localhost:8675/") as response:
417
+ async with session.get("http: / / localhost: 8675 /") as response:
418
418
  return response.status == 200
419
419
  except Exception:
420
420
  return False
@@ -425,7 +425,7 @@ class CrackerjackDashboard(App):
425
425
 
426
426
  cpu_percent = psutil.cpu_percent(interval=None)
427
427
  memory = psutil.virtual_memory()
428
- memory_mb = memory.used // (1024 * 1024)
428
+ memory_mb = memory.used / (1024 * 1024)
429
429
 
430
430
  cpu_metric = self.query_one("#cpu_metric", MetricCard)
431
431
  memory_metric = self.query_one("#memory_metric", MetricCard)
@@ -459,7 +459,9 @@ class CrackerjackDashboard(App):
459
459
  try:
460
460
  timeout = aiohttp.ClientTimeout(total=10.0)
461
461
  async with aiohttp.ClientSession(timeout=timeout) as session:
462
- async with session.get("http://localhost:8675/api/jobs") as response:
462
+ async with session.get(
463
+ "http: / / localhost: 8675 / api / jobs"
464
+ ) as response:
463
465
  if response.status == 200:
464
466
  return await response.json()
465
467
  return {}
@@ -474,19 +476,20 @@ class CrackerjackDashboard(App):
474
476
  import tempfile
475
477
 
476
478
  temp_dir = Path(tempfile.gettempdir())
477
- debug_files = temp_dir.glob("crackerjack-debug-*.log")
479
+ debug_files = temp_dir.glob("crackerjack - debug-*.log")
478
480
  for debug_file in debug_files:
479
481
  try:
480
482
  if debug_file.stat().st_mtime > (time.time() - 3600):
481
483
  debug_file.read_text()
482
- job_id = debug_file.stem.replace("crackerjack-debug-", "")
484
+ job_id = debug_file.stem.replace("crackerjack-debug -", "")
483
485
  jobs[job_id] = {
484
486
  "id": job_id,
485
487
  "status": "completed",
486
488
  "log_file": str(debug_file),
487
489
  "timestamp": debug_file.stat().st_mtime,
488
490
  }
489
- except Exception:
491
+ except Exception as e:
492
+ self.log(f"Could not process debug file {debug_file}: {e}")
490
493
  continue
491
494
 
492
495
  return jobs
@@ -512,10 +515,10 @@ class CrackerjackDashboard(App):
512
515
  reverse=True,
513
516
  ):
514
517
  status = job_data.get("status", "unknown")
515
- stage = job_data.get("current_stage", "N/A")
518
+ stage = job_data.get("current_stage", "N / A")
516
519
  progress = job_data.get("progress", 0)
517
520
  started = datetime.fromtimestamp(job_data.get("timestamp", 0)).strftime(
518
- "%H:%M:%S",
521
+ "% H: % M: % S",
519
522
  )
520
523
  duration = self._format_duration(job_data.get("duration", 0))
521
524
  issues = job_data.get("issues_found", 0)
@@ -577,7 +580,7 @@ class CrackerjackDashboard(App):
577
580
  try:
578
581
  log_display = self.query_one("#log_display", Log)
579
582
 
580
- current_time = datetime.now().strftime("%H:%M:%S")
583
+ current_time = datetime.now().strftime("% H: % M: % S")
581
584
  if len(self.logs_buffer) % 10 == 0:
582
585
  log_display.write_line(f"[{current_time}] Dashboard refresh completed")
583
586