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
@@ -10,6 +10,8 @@ from typing import Any
10
10
  import aiohttp
11
11
  from rich.console import Console
12
12
 
13
+ from crackerjack.core.timeout_manager import get_timeout_manager
14
+
13
15
 
14
16
  class JobDataCollector:
15
17
  def __init__(self, progress_dir: Path, websocket_url: str) -> None:
@@ -48,7 +50,7 @@ class JobDataCollector:
48
50
  if not self.progress_dir.exists():
49
51
  return jobs_data
50
52
 
51
- for progress_file in self.progress_dir.glob("job -* .json"):
53
+ for progress_file in self.progress_dir.glob("job-* .json"):
52
54
  self._process_progress_file(progress_file, jobs_data)
53
55
 
54
56
  return jobs_data
@@ -62,7 +64,7 @@ class JobDataCollector:
62
64
  with progress_file.open() as f:
63
65
  data = json.load(f)
64
66
 
65
- job_id = progress_file.stem.replace("job - ", "")
67
+ job_id = progress_file.stem.replace("job-", "")
66
68
  self._update_job_counters(data, jobs_data)
67
69
  self._aggregate_error_metrics(data, jobs_data)
68
70
  self._add_individual_job(job_id, data, jobs_data)
@@ -148,56 +150,62 @@ class JobDataCollector:
148
150
  "current_errors": 0,
149
151
  }
150
152
 
151
- with suppress(Exception):
152
- websocket_base = self.websocket_url.replace("ws: // ", "http: // ").replace(
153
- "wss: // ",
154
- "https: // ",
155
- )
153
+ timeout_manager = get_timeout_manager()
156
154
 
157
- async with (
158
- aiohttp.ClientSession(
159
- timeout=aiohttp.ClientTimeout(total=3),
160
- ) as session,
161
- session.get(f"{websocket_base} / ") as response,
155
+ with suppress(Exception):
156
+ async with timeout_manager.timeout_context(
157
+ "network_operations",
158
+ timeout=5.0, # Short timeout for websocket discovery
162
159
  ):
163
- if response.status == 200:
164
- data = await response.json()
165
-
166
- active_jobs = data.get("active_jobs_detailed", [])
167
-
168
- for job in active_jobs:
169
- job_id = job.get("job_id", "unknown")
170
- status = job.get("status", "unknown")
171
-
172
- if status == "running":
173
- jobs_data["active"] += 1
174
- elif status == "completed":
175
- jobs_data["completed"] += 1
176
- elif status == "failed":
177
- jobs_data["failed"] += 1
178
-
179
- jobs_data["total"] += 1
180
-
181
- job_entry = {
182
- "job_id": job_id,
183
- "status": status,
184
- "iteration": job.get("iteration", 1),
185
- "max_iterations": job.get("max_iterations", 10),
186
- "current_stage": job.get("current_stage", "unknown"),
187
- "message": job.get("message", "Processing..."),
188
- "project": job.get("project", "crackerjack"),
189
- "total_issues": job.get("total_issues", 0),
190
- "errors_fixed": job.get("errors_fixed", 0),
191
- "errors_failed": job.get("errors_failed", 0),
192
- "current_errors": job.get("current_errors", 0),
193
- "overall_progress": job.get("overall_progress", 0.0),
194
- "stage_progress": job.get("stage_progress", 0.0),
195
- }
196
- jobs_data["individual_jobs"].append(job_entry)
197
-
198
- jobs_data["total_issues"] += job.get("total_issues", 0)
199
- jobs_data["errors_fixed"] += job.get("errors_fixed", 0)
200
- jobs_data["errors_failed"] += job.get("errors_failed", 0)
160
+ websocket_base = self.websocket_url.replace("ws://", "http://").replace(
161
+ "wss://",
162
+ "https://",
163
+ )
164
+
165
+ async with (
166
+ aiohttp.ClientSession(
167
+ timeout=aiohttp.ClientTimeout(total=3),
168
+ ) as session,
169
+ session.get(f"{websocket_base}/") as response,
170
+ ):
171
+ if response.status == 200:
172
+ data = await response.json()
173
+
174
+ active_jobs = data.get("active_jobs_detailed", [])
175
+
176
+ for job in active_jobs:
177
+ job_id = job.get("job_id", "unknown")
178
+ status = job.get("status", "unknown")
179
+
180
+ if status == "running":
181
+ jobs_data["active"] += 1
182
+ elif status == "completed":
183
+ jobs_data["completed"] += 1
184
+ elif status == "failed":
185
+ jobs_data["failed"] += 1
186
+
187
+ jobs_data["total"] += 1
188
+
189
+ job_entry = {
190
+ "job_id": job_id,
191
+ "status": status,
192
+ "iteration": job.get("iteration", 1),
193
+ "max_iterations": job.get("max_iterations", 10),
194
+ "current_stage": job.get("current_stage", "unknown"),
195
+ "message": job.get("message", "Processing..."),
196
+ "project": job.get("project", "crackerjack"),
197
+ "total_issues": job.get("total_issues", 0),
198
+ "errors_fixed": job.get("errors_fixed", 0),
199
+ "errors_failed": job.get("errors_failed", 0),
200
+ "current_errors": job.get("current_errors", 0),
201
+ "overall_progress": job.get("overall_progress", 0.0),
202
+ "stage_progress": job.get("stage_progress", 0.0),
203
+ }
204
+ jobs_data["individual_jobs"].append(job_entry)
205
+
206
+ jobs_data["total_issues"] += job.get("total_issues", 0)
207
+ jobs_data["errors_fixed"] += job.get("errors_fixed", 0)
208
+ jobs_data["errors_failed"] += job.get("errors_failed", 0)
201
209
 
202
210
  return jobs_data
203
211
 
@@ -226,23 +234,29 @@ class ServiceHealthChecker:
226
234
  return services
227
235
 
228
236
  async def _check_websocket_server(self) -> tuple[str, str, str]:
237
+ timeout_manager = get_timeout_manager()
238
+
229
239
  try:
230
- async with (
231
- aiohttp.ClientSession(
232
- timeout=aiohttp.ClientTimeout(total=2),
233
- ) as session,
234
- session.get("http: // localhost: 8675 / ") as response,
240
+ async with timeout_manager.timeout_context(
241
+ "network_operations",
242
+ timeout=3.0, # Quick health check timeout
235
243
  ):
236
- if response.status == 200:
237
- data = await response.json()
238
- connections = data.get("total_connections", 0)
239
- len(data.get("active_jobs", []))
240
- return (
241
- "WebSocket Server",
242
- f"🟢 Active ({connections} conn)",
243
- "0",
244
- )
245
- return ("WebSocket Server", "🔴 HTTP Error", "1")
244
+ async with (
245
+ aiohttp.ClientSession(
246
+ timeout=aiohttp.ClientTimeout(total=2),
247
+ ) as session,
248
+ session.get("http://localhost:8675/") as response,
249
+ ):
250
+ if response.status == 200:
251
+ data = await response.json()
252
+ connections = data.get("total_connections", 0)
253
+ len(data.get("active_jobs", []))
254
+ return (
255
+ "WebSocket Server",
256
+ f"🟢 Active ({connections} conn)",
257
+ "0",
258
+ )
259
+ return ("WebSocket Server", "🔴 HTTP Error", "1")
246
260
  except Exception:
247
261
  return ("WebSocket Server", "🔴 Connection Failed", "1")
248
262
 
@@ -253,10 +267,13 @@ class ServiceHealthChecker:
253
267
  check=False,
254
268
  capture_output=True,
255
269
  text=True,
270
+ timeout=5.0, # Add timeout protection
256
271
  )
257
272
  if result.returncode == 0:
258
273
  return ("MCP Server", "🟢 Process Active", "0")
259
274
  return ("MCP Server", "🔴 No Process", "0")
275
+ except subprocess.TimeoutExpired:
276
+ return ("MCP Server", "🔴 Process Check Timeout", "0")
260
277
  except Exception:
261
278
  return ("MCP Server", "🔴 Check Failed", "0")
262
279
 
@@ -267,10 +284,13 @@ class ServiceHealthChecker:
267
284
  check=False,
268
285
  capture_output=True,
269
286
  text=True,
287
+ timeout=5.0, # Add timeout protection
270
288
  )
271
289
  if result.returncode == 0:
272
290
  return ("Service Watchdog", "🟢 Active", "0")
273
291
  return ("Service Watchdog", "🔴 Inactive", "0")
292
+ except subprocess.TimeoutExpired:
293
+ return ("Service Watchdog", "🔴 Process Check Timeout", "0")
274
294
  except Exception:
275
295
  return ("Service Watchdog", "🔴 Check Failed", "0")
276
296
 
@@ -294,13 +314,12 @@ class ErrorCollector:
294
314
  "No recent errors found",
295
315
  "Clean",
296
316
  ),
297
- (" -- : -- : -- ", "monitor", "System monitoring active", "Status"),
317
+ (" - - : - - : --", "monitor", "System monitoring active", "Status"),
298
318
  ]
299
319
 
300
320
  return errors[-5:]
301
321
 
302
322
  def _check_debug_logs(self) -> list[tuple[str, str, str, str]]:
303
- """Check debug logs for recent errors."""
304
323
  errors = []
305
324
 
306
325
  with suppress(Exception):
@@ -314,7 +333,6 @@ class ErrorCollector:
314
333
  self,
315
334
  debug_log: Path,
316
335
  ) -> list[tuple[str, str, str, str]]:
317
- """Extract error entries from debug log file."""
318
336
  errors = []
319
337
 
320
338
  with debug_log.open() as f:
@@ -329,11 +347,9 @@ class ErrorCollector:
329
347
  return errors
330
348
 
331
349
  def _is_debug_error_line(self, line: str) -> bool:
332
- """Check if debug log line contains error indicators."""
333
350
  return any(indicator in line for indicator in ("ERROR", "Exception", "Failed"))
334
351
 
335
352
  def _parse_debug_error_line(self, line: str) -> tuple[str, str, str, str] | None:
336
- """Parse debug error line into error entry tuple."""
337
353
  parts = line.strip().split(" ", 2)
338
354
  if len(parts) < 3:
339
355
  return None
@@ -343,29 +359,27 @@ class ErrorCollector:
343
359
  return (timestamp, "debug", error_msg, "System")
344
360
 
345
361
  def _truncate_debug_message(self, message: str) -> str:
346
- """Truncate debug message to reasonable length."""
347
362
  return message[:40] + "..." if len(message) > 40 else message
348
363
 
349
364
  def _check_crackerjack_logs(self) -> list[tuple[str, str, str, str]]:
350
- """Check crackerjack debug logs for recent errors."""
351
365
  errors = []
352
366
 
353
367
  with suppress(Exception):
354
- for log_file in Path(tempfile.gettempdir()).glob("crackerjack-debug-*.log"):
368
+ for log_file in Path(tempfile.gettempdir()).glob(
369
+ "crackerjack - debug-*.log"
370
+ ):
355
371
  if self._is_log_file_recent(log_file):
356
372
  errors.extend(self._extract_errors_from_log_file(log_file))
357
373
 
358
374
  return errors
359
375
 
360
376
  def _is_log_file_recent(self, log_file: Path) -> bool:
361
- """Check if log file was modified within the last hour."""
362
377
  return time.time() - log_file.stat().st_mtime < 3600
363
378
 
364
379
  def _extract_errors_from_log_file(
365
380
  self,
366
381
  log_file: Path,
367
382
  ) -> list[tuple[str, str, str, str]]:
368
- """Extract error entries from a single log file."""
369
383
  errors = []
370
384
 
371
385
  with log_file.open() as f:
@@ -379,7 +393,6 @@ class ErrorCollector:
379
393
  return errors
380
394
 
381
395
  def _is_error_line(self, line: str) -> bool:
382
- """Check if a log line contains error keywords."""
383
396
  return any(
384
397
  keyword in line.lower() for keyword in ("error", "failed", "exception")
385
398
  )
@@ -389,7 +402,6 @@ class ErrorCollector:
389
402
  line: str,
390
403
  log_file: Path,
391
404
  ) -> tuple[str, str, str, str]:
392
- """Create error entry tuple from log line and file."""
393
405
  timestamp = time.strftime(
394
406
  " % H: % M: % S",
395
407
  time.localtime(log_file.stat().st_mtime),
@@ -398,7 +410,6 @@ class ErrorCollector:
398
410
  return (timestamp, "job", error_msg, "Crackerjack")
399
411
 
400
412
  def _truncate_error_message(self, message: str) -> str:
401
- """Truncate error message to reasonable length."""
402
413
  return message[:50] + "..." if len(message) > 50 else message
403
414
 
404
415
 
@@ -430,12 +441,18 @@ class ServiceManager:
430
441
  ]
431
442
 
432
443
  async def _check_websocket_server(self) -> bool:
444
+ timeout_manager = get_timeout_manager()
445
+
433
446
  with suppress(Exception):
434
- async with aiohttp.ClientSession(
435
- timeout=aiohttp.ClientTimeout(total=2),
436
- ) as session:
437
- async with session.get("http: // localhost: 8675 / ") as response:
438
- return response.status == 200
447
+ async with timeout_manager.timeout_context(
448
+ "network_operations",
449
+ timeout=3.0, # Quick check timeout
450
+ ):
451
+ async with aiohttp.ClientSession(
452
+ timeout=aiohttp.ClientTimeout(total=2),
453
+ ) as session:
454
+ async with session.get("http://localhost:8675/") as response:
455
+ return response.status == 200
439
456
  return False
440
457
 
441
458
  def _check_mcp_server(self) -> bool:
@@ -445,6 +462,7 @@ class ServiceManager:
445
462
  check=False,
446
463
  capture_output=True,
447
464
  text=True,
465
+ timeout=5.0, # Add timeout protection
448
466
  )
449
467
  return result.returncode == 0
450
468
  return False
@@ -478,12 +496,13 @@ class ServiceManager:
478
496
  check=False,
479
497
  capture_output=True,
480
498
  text=True,
499
+ timeout=5.0, # Add timeout protection
481
500
  )
482
501
  if result.returncode == 0:
483
502
  return
484
503
 
485
504
  process = subprocess.Popen(
486
- ["python", " - m", "crackerjack", " -- watchdog"],
505
+ ["python", "-m", "crackerjack", "--watchdog"],
487
506
  stdout=subprocess.PIPE,
488
507
  stderr=subprocess.PIPE,
489
508
  start_new_session=True,