crackerjack 0.33.0__py3-none-any.whl → 0.33.2__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 (198) hide show
  1. crackerjack/__main__.py +1350 -34
  2. crackerjack/adapters/__init__.py +17 -0
  3. crackerjack/adapters/lsp_client.py +358 -0
  4. crackerjack/adapters/rust_tool_adapter.py +194 -0
  5. crackerjack/adapters/rust_tool_manager.py +193 -0
  6. crackerjack/adapters/skylos_adapter.py +231 -0
  7. crackerjack/adapters/zuban_adapter.py +560 -0
  8. crackerjack/agents/base.py +7 -3
  9. crackerjack/agents/coordinator.py +271 -33
  10. crackerjack/agents/documentation_agent.py +9 -15
  11. crackerjack/agents/dry_agent.py +3 -15
  12. crackerjack/agents/formatting_agent.py +1 -1
  13. crackerjack/agents/import_optimization_agent.py +36 -180
  14. crackerjack/agents/performance_agent.py +17 -98
  15. crackerjack/agents/performance_helpers.py +7 -31
  16. crackerjack/agents/proactive_agent.py +1 -3
  17. crackerjack/agents/refactoring_agent.py +16 -85
  18. crackerjack/agents/refactoring_helpers.py +7 -42
  19. crackerjack/agents/security_agent.py +9 -48
  20. crackerjack/agents/test_creation_agent.py +356 -513
  21. crackerjack/agents/test_specialist_agent.py +0 -4
  22. crackerjack/api.py +6 -25
  23. crackerjack/cli/cache_handlers.py +204 -0
  24. crackerjack/cli/cache_handlers_enhanced.py +683 -0
  25. crackerjack/cli/facade.py +100 -0
  26. crackerjack/cli/handlers.py +224 -9
  27. crackerjack/cli/interactive.py +6 -4
  28. crackerjack/cli/options.py +642 -55
  29. crackerjack/cli/utils.py +2 -1
  30. crackerjack/code_cleaner.py +58 -117
  31. crackerjack/config/global_lock_config.py +8 -48
  32. crackerjack/config/hooks.py +53 -62
  33. crackerjack/core/async_workflow_orchestrator.py +24 -34
  34. crackerjack/core/autofix_coordinator.py +3 -17
  35. crackerjack/core/enhanced_container.py +4 -13
  36. crackerjack/core/file_lifecycle.py +12 -89
  37. crackerjack/core/performance.py +2 -2
  38. crackerjack/core/performance_monitor.py +15 -55
  39. crackerjack/core/phase_coordinator.py +104 -204
  40. crackerjack/core/resource_manager.py +14 -90
  41. crackerjack/core/service_watchdog.py +62 -95
  42. crackerjack/core/session_coordinator.py +149 -0
  43. crackerjack/core/timeout_manager.py +14 -72
  44. crackerjack/core/websocket_lifecycle.py +13 -78
  45. crackerjack/core/workflow_orchestrator.py +171 -174
  46. crackerjack/docs/INDEX.md +11 -0
  47. crackerjack/docs/generated/api/API_REFERENCE.md +10895 -0
  48. crackerjack/docs/generated/api/CLI_REFERENCE.md +109 -0
  49. crackerjack/docs/generated/api/CROSS_REFERENCES.md +1755 -0
  50. crackerjack/docs/generated/api/PROTOCOLS.md +3 -0
  51. crackerjack/docs/generated/api/SERVICES.md +1252 -0
  52. crackerjack/documentation/__init__.py +31 -0
  53. crackerjack/documentation/ai_templates.py +756 -0
  54. crackerjack/documentation/dual_output_generator.py +765 -0
  55. crackerjack/documentation/mkdocs_integration.py +518 -0
  56. crackerjack/documentation/reference_generator.py +977 -0
  57. crackerjack/dynamic_config.py +55 -50
  58. crackerjack/executors/async_hook_executor.py +10 -15
  59. crackerjack/executors/cached_hook_executor.py +117 -43
  60. crackerjack/executors/hook_executor.py +8 -34
  61. crackerjack/executors/hook_lock_manager.py +26 -183
  62. crackerjack/executors/individual_hook_executor.py +13 -11
  63. crackerjack/executors/lsp_aware_hook_executor.py +270 -0
  64. crackerjack/executors/tool_proxy.py +417 -0
  65. crackerjack/hooks/lsp_hook.py +79 -0
  66. crackerjack/intelligence/adaptive_learning.py +25 -10
  67. crackerjack/intelligence/agent_orchestrator.py +2 -5
  68. crackerjack/intelligence/agent_registry.py +34 -24
  69. crackerjack/intelligence/agent_selector.py +5 -7
  70. crackerjack/interactive.py +17 -6
  71. crackerjack/managers/async_hook_manager.py +0 -1
  72. crackerjack/managers/hook_manager.py +79 -1
  73. crackerjack/managers/publish_manager.py +44 -8
  74. crackerjack/managers/test_command_builder.py +1 -15
  75. crackerjack/managers/test_executor.py +1 -3
  76. crackerjack/managers/test_manager.py +98 -7
  77. crackerjack/managers/test_manager_backup.py +10 -9
  78. crackerjack/mcp/cache.py +2 -2
  79. crackerjack/mcp/client_runner.py +1 -1
  80. crackerjack/mcp/context.py +191 -68
  81. crackerjack/mcp/dashboard.py +7 -5
  82. crackerjack/mcp/enhanced_progress_monitor.py +31 -28
  83. crackerjack/mcp/file_monitor.py +30 -23
  84. crackerjack/mcp/progress_components.py +31 -21
  85. crackerjack/mcp/progress_monitor.py +50 -53
  86. crackerjack/mcp/rate_limiter.py +6 -6
  87. crackerjack/mcp/server_core.py +17 -16
  88. crackerjack/mcp/service_watchdog.py +2 -1
  89. crackerjack/mcp/state.py +4 -7
  90. crackerjack/mcp/task_manager.py +11 -9
  91. crackerjack/mcp/tools/core_tools.py +173 -32
  92. crackerjack/mcp/tools/error_analyzer.py +3 -2
  93. crackerjack/mcp/tools/execution_tools.py +8 -10
  94. crackerjack/mcp/tools/execution_tools_backup.py +42 -30
  95. crackerjack/mcp/tools/intelligence_tool_registry.py +7 -5
  96. crackerjack/mcp/tools/intelligence_tools.py +5 -2
  97. crackerjack/mcp/tools/monitoring_tools.py +33 -70
  98. crackerjack/mcp/tools/proactive_tools.py +24 -11
  99. crackerjack/mcp/tools/progress_tools.py +5 -8
  100. crackerjack/mcp/tools/utility_tools.py +20 -14
  101. crackerjack/mcp/tools/workflow_executor.py +62 -40
  102. crackerjack/mcp/websocket/app.py +8 -0
  103. crackerjack/mcp/websocket/endpoints.py +352 -357
  104. crackerjack/mcp/websocket/jobs.py +40 -57
  105. crackerjack/mcp/websocket/monitoring_endpoints.py +2935 -0
  106. crackerjack/mcp/websocket/server.py +7 -25
  107. crackerjack/mcp/websocket/websocket_handler.py +6 -17
  108. crackerjack/mixins/__init__.py +0 -2
  109. crackerjack/mixins/error_handling.py +1 -70
  110. crackerjack/models/config.py +12 -1
  111. crackerjack/models/config_adapter.py +49 -1
  112. crackerjack/models/protocols.py +122 -122
  113. crackerjack/models/resource_protocols.py +55 -210
  114. crackerjack/monitoring/ai_agent_watchdog.py +13 -13
  115. crackerjack/monitoring/metrics_collector.py +426 -0
  116. crackerjack/monitoring/regression_prevention.py +8 -8
  117. crackerjack/monitoring/websocket_server.py +643 -0
  118. crackerjack/orchestration/advanced_orchestrator.py +11 -6
  119. crackerjack/orchestration/coverage_improvement.py +3 -3
  120. crackerjack/orchestration/execution_strategies.py +26 -6
  121. crackerjack/orchestration/test_progress_streamer.py +8 -5
  122. crackerjack/plugins/base.py +2 -2
  123. crackerjack/plugins/hooks.py +7 -0
  124. crackerjack/plugins/managers.py +11 -8
  125. crackerjack/security/__init__.py +0 -1
  126. crackerjack/security/audit.py +6 -35
  127. crackerjack/services/anomaly_detector.py +392 -0
  128. crackerjack/services/api_extractor.py +615 -0
  129. crackerjack/services/backup_service.py +2 -2
  130. crackerjack/services/bounded_status_operations.py +15 -152
  131. crackerjack/services/cache.py +127 -1
  132. crackerjack/services/changelog_automation.py +395 -0
  133. crackerjack/services/config.py +15 -9
  134. crackerjack/services/config_merge.py +19 -80
  135. crackerjack/services/config_template.py +506 -0
  136. crackerjack/services/contextual_ai_assistant.py +48 -22
  137. crackerjack/services/coverage_badge_service.py +171 -0
  138. crackerjack/services/coverage_ratchet.py +27 -25
  139. crackerjack/services/debug.py +3 -3
  140. crackerjack/services/dependency_analyzer.py +460 -0
  141. crackerjack/services/dependency_monitor.py +14 -11
  142. crackerjack/services/documentation_generator.py +491 -0
  143. crackerjack/services/documentation_service.py +675 -0
  144. crackerjack/services/enhanced_filesystem.py +6 -5
  145. crackerjack/services/enterprise_optimizer.py +865 -0
  146. crackerjack/services/error_pattern_analyzer.py +676 -0
  147. crackerjack/services/file_hasher.py +1 -1
  148. crackerjack/services/git.py +8 -25
  149. crackerjack/services/health_metrics.py +10 -8
  150. crackerjack/services/heatmap_generator.py +735 -0
  151. crackerjack/services/initialization.py +11 -30
  152. crackerjack/services/input_validator.py +5 -97
  153. crackerjack/services/intelligent_commit.py +327 -0
  154. crackerjack/services/log_manager.py +15 -12
  155. crackerjack/services/logging.py +4 -3
  156. crackerjack/services/lsp_client.py +628 -0
  157. crackerjack/services/memory_optimizer.py +19 -87
  158. crackerjack/services/metrics.py +42 -33
  159. crackerjack/services/parallel_executor.py +9 -67
  160. crackerjack/services/pattern_cache.py +1 -1
  161. crackerjack/services/pattern_detector.py +6 -6
  162. crackerjack/services/performance_benchmarks.py +18 -59
  163. crackerjack/services/performance_cache.py +20 -81
  164. crackerjack/services/performance_monitor.py +27 -95
  165. crackerjack/services/predictive_analytics.py +510 -0
  166. crackerjack/services/quality_baseline.py +234 -0
  167. crackerjack/services/quality_baseline_enhanced.py +646 -0
  168. crackerjack/services/quality_intelligence.py +785 -0
  169. crackerjack/services/regex_patterns.py +618 -524
  170. crackerjack/services/regex_utils.py +43 -123
  171. crackerjack/services/secure_path_utils.py +5 -164
  172. crackerjack/services/secure_status_formatter.py +30 -141
  173. crackerjack/services/secure_subprocess.py +11 -92
  174. crackerjack/services/security.py +9 -41
  175. crackerjack/services/security_logger.py +12 -24
  176. crackerjack/services/server_manager.py +124 -16
  177. crackerjack/services/status_authentication.py +16 -159
  178. crackerjack/services/status_security_manager.py +4 -131
  179. crackerjack/services/thread_safe_status_collector.py +19 -125
  180. crackerjack/services/unified_config.py +21 -13
  181. crackerjack/services/validation_rate_limiter.py +5 -54
  182. crackerjack/services/version_analyzer.py +459 -0
  183. crackerjack/services/version_checker.py +1 -1
  184. crackerjack/services/websocket_resource_limiter.py +10 -144
  185. crackerjack/services/zuban_lsp_service.py +390 -0
  186. crackerjack/slash_commands/__init__.py +2 -7
  187. crackerjack/slash_commands/run.md +2 -2
  188. crackerjack/tools/validate_input_validator_patterns.py +14 -40
  189. crackerjack/tools/validate_regex_patterns.py +19 -48
  190. {crackerjack-0.33.0.dist-info → crackerjack-0.33.2.dist-info}/METADATA +196 -25
  191. crackerjack-0.33.2.dist-info/RECORD +229 -0
  192. crackerjack/CLAUDE.md +0 -207
  193. crackerjack/RULES.md +0 -380
  194. crackerjack/py313.py +0 -234
  195. crackerjack-0.33.0.dist-info/RECORD +0 -187
  196. {crackerjack-0.33.0.dist-info → crackerjack-0.33.2.dist-info}/WHEEL +0 -0
  197. {crackerjack-0.33.0.dist-info → crackerjack-0.33.2.dist-info}/entry_points.txt +0 -0
  198. {crackerjack-0.33.0.dist-info → crackerjack-0.33.2.dist-info}/licenses/LICENSE +0 -0
crackerjack/cli/utils.py CHANGED
@@ -13,6 +13,7 @@ def get_package_version() -> str:
13
13
  pyproject_path = Path(__file__).parent.parent.parent / "pyproject.toml"
14
14
  with pyproject_path.open("rb") as f:
15
15
  pyproject_data = tomllib.load(f)
16
- return pyproject_data["project"]["version"]
16
+ version_value = pyproject_data["project"]["version"]
17
+ return str(version_value)
17
18
  except Exception:
18
19
  return "unknown"
@@ -22,31 +22,31 @@ from .services.security_logger import (
22
22
 
23
23
 
24
24
  class SafePatternApplicator:
25
- """Safe pattern applicator using centralized SAFE_PATTERNS."""
26
-
27
25
  def apply_docstring_patterns(self, code: str) -> str:
28
- """Apply docstring removal patterns safely."""
29
26
  result = code
30
27
  result = SAFE_PATTERNS["docstring_triple_double"].apply(result)
31
28
  result = SAFE_PATTERNS["docstring_triple_single"].apply(result)
32
29
  return result
33
30
 
34
31
  def apply_formatting_patterns(self, content: str) -> str:
35
- """Apply formatting patterns safely."""
36
- # Apply spacing patterns
37
32
  content = SAFE_PATTERNS["spacing_after_comma"].apply(content)
38
33
  content = SAFE_PATTERNS["spacing_after_colon"].apply(content)
39
34
  content = SAFE_PATTERNS["multiple_spaces"].apply(content)
40
35
  return content
41
36
 
42
37
  def has_preserved_comment(self, line: str) -> bool:
43
- """Check if a line contains preserved comments."""
44
38
  if line.strip().startswith("#! /"):
45
39
  return True
46
40
 
47
- # Check for preserved comment keywords
48
41
  line_lower = line.lower()
49
- preserved_keywords = ["coding:", "encoding:", "type:", "noqa", "pragma"]
42
+ preserved_keywords = [
43
+ "coding: ",
44
+ "encoding: ",
45
+ "type: ",
46
+ "noqa",
47
+ "pragma",
48
+ "regex ok",
49
+ ]
50
50
  return any(keyword in line_lower for keyword in preserved_keywords)
51
51
 
52
52
 
@@ -421,22 +421,13 @@ class CodeCleaner(BaseModel):
421
421
  self._create_formatting_step(),
422
422
  ]
423
423
 
424
- return self.pipeline.clean_file(file_path, cleaning_steps)
424
+ result = self.pipeline.clean_file(file_path, cleaning_steps)
425
+ return t.cast(CleaningResult, result)
425
426
 
426
427
  def clean_files(
427
428
  self, pkg_dir: Path | None = None, use_backup: bool = True
428
429
  ) -> list[CleaningResult] | PackageCleaningResult:
429
- """Clean package files with optional backup protection.
430
-
431
- Args:
432
- pkg_dir: Package directory to clean (defaults to current directory)
433
- use_backup: Whether to use backup protection (default: True for safety)
434
-
435
- Returns:
436
- PackageCleaningResult with backup protection (default), list[CleaningResult] if use_backup=False (legacy)
437
- """
438
430
  if use_backup:
439
- # Use the comprehensive backup system for maximum safety
440
431
  package_result = self.clean_files_with_backup(pkg_dir)
441
432
  self.logger.info(
442
433
  f"Package cleaning with backup completed: "
@@ -445,7 +436,6 @@ class CodeCleaner(BaseModel):
445
436
  )
446
437
  return package_result
447
438
 
448
- # Legacy non-backup mode (deprecated, kept for compatibility)
449
439
  self.console.print(
450
440
  "[yellow]⚠️ WARNING: Running without backup protection. "
451
441
  "Consider using use_backup=True for safety.[/yellow]"
@@ -519,9 +509,10 @@ class CodeCleaner(BaseModel):
519
509
  "[yellow]📦 Creating backup of all package files...[/yellow]"
520
510
  )
521
511
 
522
- backup_metadata = self.backup_service.create_package_backup(
512
+ backup_result = self.backup_service.create_package_backup(
523
513
  validated_pkg_dir, self.base_directory
524
514
  )
515
+ backup_metadata: BackupMetadata = t.cast(BackupMetadata, backup_result)
525
516
 
526
517
  self.console.print(
527
518
  f"[green]✅ Backup created: {backup_metadata.backup_id}[/green] "
@@ -539,23 +530,9 @@ class CodeCleaner(BaseModel):
539
530
  ]
540
531
 
541
532
  def _discover_package_files(self, root_dir: Path) -> list[Path]:
542
- """Discover Python files in the main package directory using crackerjack naming convention.
543
-
544
- Crackerjack convention:
545
- - Project name with dashes → package name with underscores
546
- - Single word → same name lowercase
547
- - Package directory determined from pyproject.toml [project.name]
548
-
549
- Args:
550
- root_dir: Project root directory
551
-
552
- Returns:
553
- List of Python files found only in the main package directory
554
- """
555
533
  package_dir = self._find_package_directory(root_dir)
556
534
 
557
535
  if not package_dir or not package_dir.exists():
558
- # Fallback: look for any directory with __init__.py (excluding common non-package dirs)
559
536
  self.console.print(
560
537
  "[yellow]⚠️ Could not determine package directory, searching for Python packages...[/yellow]"
561
538
  )
@@ -563,10 +540,8 @@ class CodeCleaner(BaseModel):
563
540
 
564
541
  self.logger.debug(f"Using package directory: {package_dir}")
565
542
 
566
- # Get all Python files from the package directory only
567
- package_files = list(package_dir.rglob("*.py"))
543
+ package_files = list[t.Any](package_dir.rglob("*.py"))
568
544
 
569
- # Filter out any problematic subdirectories that might exist within the package
570
545
  exclude_dirs = {
571
546
  "__pycache__",
572
547
  ".pytest_cache",
@@ -584,15 +559,6 @@ class CodeCleaner(BaseModel):
584
559
  return filtered_files
585
560
 
586
561
  def _find_package_directory(self, root_dir: Path) -> Path | None:
587
- """Find the main package directory using crackerjack naming convention.
588
-
589
- Args:
590
- root_dir: Project root directory
591
-
592
- Returns:
593
- Path to package directory or None if not found
594
- """
595
- # First, try to get project name from pyproject.toml
596
562
  pyproject_path = root_dir / "pyproject.toml"
597
563
  if pyproject_path.exists():
598
564
  try:
@@ -601,9 +567,9 @@ class CodeCleaner(BaseModel):
601
567
  with pyproject_path.open("rb") as f:
602
568
  config = tomllib.load(f)
603
569
 
604
- project_name = config.get("project", {}).get("name")
570
+ project_name_raw = config.get("project", {}).get("name")
571
+ project_name: str | None = t.cast(str | None, project_name_raw)
605
572
  if project_name:
606
- # Apply crackerjack naming convention
607
573
  package_name = project_name.replace("-", "_").lower()
608
574
  package_dir = root_dir / package_name
609
575
 
@@ -613,7 +579,6 @@ class CodeCleaner(BaseModel):
613
579
  except Exception as e:
614
580
  self.logger.debug(f"Could not parse pyproject.toml: {e}")
615
581
 
616
- # Fallback: infer from directory name
617
582
  package_name = root_dir.name.replace("-", "_").lower()
618
583
  package_dir = root_dir / package_name
619
584
 
@@ -623,7 +588,6 @@ class CodeCleaner(BaseModel):
623
588
  return None
624
589
 
625
590
  def _fallback_discover_packages(self, root_dir: Path) -> list[Path]:
626
- """Fallback method to discover package files when convention-based detection fails."""
627
591
  python_files = []
628
592
  exclude_dirs = {
629
593
  "__pycache__",
@@ -669,11 +633,8 @@ class CodeCleaner(BaseModel):
669
633
  def _should_include_file_path(
670
634
  self, file_path: Path, exclude_dirs: set[str]
671
635
  ) -> bool:
672
- """Check if a file path should be included (not in excluded directories)."""
673
- # Convert path parts to set for efficient lookup
674
- path_parts = set(file_path.parts)
636
+ path_parts = set[t.Any](file_path.parts)
675
637
 
676
- # If any part of the path is in exclude_dirs, exclude it
677
638
  return not bool(path_parts.intersection(exclude_dirs))
678
639
 
679
640
  def _handle_no_files_to_process(
@@ -864,11 +825,6 @@ class CodeCleaner(BaseModel):
864
825
  return False
865
826
 
866
827
  def restore_from_backup_metadata(self, backup_metadata: BackupMetadata) -> None:
867
- """Manually restore from backup metadata.
868
-
869
- Args:
870
- backup_metadata: Backup metadata containing restoration information
871
- """
872
828
  self.console.print(
873
829
  f"[yellow]🔄 Manually restoring from backup: {backup_metadata.backup_id}[/yellow]"
874
830
  )
@@ -881,14 +837,6 @@ class CodeCleaner(BaseModel):
881
837
  )
882
838
 
883
839
  def create_emergency_backup(self, pkg_dir: Path | None = None) -> BackupMetadata:
884
- """Create an emergency backup before potentially risky operations.
885
-
886
- Args:
887
- pkg_dir: Package directory to backup (defaults to current directory)
888
-
889
- Returns:
890
- BackupMetadata for the created backup
891
- """
892
840
  validated_pkg_dir = self._prepare_package_directory(pkg_dir)
893
841
 
894
842
  self.console.print(
@@ -904,14 +852,6 @@ class CodeCleaner(BaseModel):
904
852
  return backup_metadata
905
853
 
906
854
  def restore_emergency_backup(self, backup_metadata: BackupMetadata) -> bool:
907
- """Restore from an emergency backup with enhanced error handling.
908
-
909
- Args:
910
- backup_metadata: Backup metadata for restoration
911
-
912
- Returns:
913
- True if restoration succeeded, False otherwise
914
- """
915
855
  try:
916
856
  self.console.print(
917
857
  f"[yellow]🔄 Restoring emergency backup: {backup_metadata.backup_id}[/yellow]"
@@ -938,14 +878,6 @@ class CodeCleaner(BaseModel):
938
878
  return False
939
879
 
940
880
  def verify_backup_integrity(self, backup_metadata: BackupMetadata) -> bool:
941
- """Verify the integrity of a backup without restoring it.
942
-
943
- Args:
944
- backup_metadata: Backup metadata to verify
945
-
946
- Returns:
947
- True if backup is valid and can be restored, False otherwise
948
- """
949
881
  try:
950
882
  validation_result = self.backup_service._validate_backup(backup_metadata)
951
883
 
@@ -960,14 +892,12 @@ class CodeCleaner(BaseModel):
960
892
  f"[red]❌ Backup verification failed: {backup_metadata.backup_id}[/red]"
961
893
  )
962
894
 
963
- for error in validation_result.validation_errors[
964
- :3
965
- ]: # Show first 3 errors
966
- self.console.print(f"[red] • {error}[/red]")
895
+ for error in validation_result.validation_errors[:3]:
896
+ self.console.print(f"[red] • {error}[/red]")
967
897
 
968
898
  if len(validation_result.validation_errors) > 3:
969
899
  remaining = len(validation_result.validation_errors) - 3
970
- self.console.print(f"[red] ... and {remaining} more errors[/red]")
900
+ self.console.print(f"[red] ... and {remaining} more errors[/red]")
971
901
 
972
902
  return False
973
903
 
@@ -977,11 +907,6 @@ class CodeCleaner(BaseModel):
977
907
  return False
978
908
 
979
909
  def list_available_backups(self) -> list[Path]:
980
- """List all available backup directories.
981
-
982
- Returns:
983
- List of backup directory paths
984
- """
985
910
  if (
986
911
  not self.backup_service.backup_root
987
912
  or not self.backup_service.backup_root.exists()
@@ -998,17 +923,17 @@ class CodeCleaner(BaseModel):
998
923
 
999
924
  if backup_dirs:
1000
925
  self.console.print(
1001
- f"[cyan]📦 Found {len(backup_dirs)} available backups:[/cyan]"
926
+ f"[cyan]📦 Found {len(backup_dirs)} available backups: [/cyan]"
1002
927
  )
1003
928
  for backup_dir in sorted(backup_dirs):
1004
- self.console.print(f" • {backup_dir.name}")
929
+ self.console.print(f" • {backup_dir.name}")
1005
930
  else:
1006
931
  self.console.print("[yellow]⚠️ No backups found[/yellow]")
1007
932
 
1008
933
  return backup_dirs
1009
934
 
1010
935
  except Exception as e:
1011
- self.logger.error(f"Failed to list backups: {e}")
936
+ self.logger.error(f"Failed to list[t.Any] backups: {e}")
1012
937
  self.console.print(f"[red]💥 Error listing backups: {e}[/red]")
1013
938
  return []
1014
939
 
@@ -1172,33 +1097,50 @@ class CodeCleaner(BaseModel):
1172
1097
  return _safe_applicator.has_preserved_comment(stripped_line)
1173
1098
 
1174
1099
  def _remove_comment_from_line(self, line: str) -> str:
1175
- if '"' not in line and "'" not in line and "#" not in line:
1100
+ """Remove comment from line while preserving strings."""
1101
+ if not self._line_needs_comment_processing(line):
1176
1102
  return line
1177
1103
 
1104
+ return self._process_line_for_comment_removal(line)
1105
+
1106
+ def _line_needs_comment_processing(self, line: str) -> bool:
1107
+ """Check if line needs comment processing."""
1108
+ return '"' in line or "'" in line or "#" in line
1109
+
1110
+ def _process_line_for_comment_removal(self, line: str) -> str:
1111
+ """Process line to remove comments while preserving strings."""
1178
1112
  result_chars = []
1179
- in_string = False
1180
- quote_char = None
1181
- i = 0
1182
- length = len(line)
1183
-
1184
- while i < length:
1185
- char = line[i]
1186
-
1187
- if not in_string:
1188
- if char == "#":
1189
- break
1190
- elif char in ('"', "'"):
1191
- in_string = True
1192
- quote_char = char
1193
- elif char == quote_char and (i == 0 or line[i - 1] != "\\"):
1194
- in_string = False
1195
- quote_char = None
1113
+ string_state = {"in_string": False, "quote_char": None}
1114
+
1115
+ for i, char in enumerate(line):
1116
+ if self._should_break_for_comment(char, string_state):
1117
+ break
1196
1118
 
1119
+ self._update_string_state(char, i, line, string_state)
1197
1120
  result_chars.append(char)
1198
- i += 1
1199
1121
 
1200
1122
  return "".join(result_chars).rstrip()
1201
1123
 
1124
+ def _should_break_for_comment(
1125
+ self, char: str, string_state: dict[str, t.Any]
1126
+ ) -> bool:
1127
+ """Check if we should break for a comment character."""
1128
+ return not string_state["in_string"] and char == "#"
1129
+
1130
+ def _update_string_state(
1131
+ self, char: str, index: int, line: str, string_state: dict[str, t.Any]
1132
+ ) -> None:
1133
+ """Update string parsing state."""
1134
+ if not string_state["in_string"]:
1135
+ if char in ('"', "'"):
1136
+ string_state["in_string"] = True
1137
+ string_state["quote_char"] = char
1138
+ elif char == string_state["quote_char"] and (
1139
+ index == 0 or line[index - 1] != "\\"
1140
+ ):
1141
+ string_state["in_string"] = False
1142
+ string_state["quote_char"] = None
1143
+
1202
1144
  def _create_docstring_finder_class(
1203
1145
  self,
1204
1146
  docstring_nodes: list[ast.AST],
@@ -1258,7 +1200,6 @@ class CodeCleaner(BaseModel):
1258
1200
  )
1259
1201
  content = cleaned_line.lstrip()
1260
1202
 
1261
- # Use SAFE_PATTERNS for multiple spaces replacement
1262
1203
  content = SAFE_PATTERNS["multiple_spaces"].apply(content)
1263
1204
 
1264
1205
  cleaned_line = cleaned_line[:leading_whitespace] + content
@@ -8,35 +8,24 @@ from ..models.protocols import OptionsProtocol
8
8
 
9
9
  @dataclass
10
10
  class GlobalLockConfig:
11
- """Configuration for global hook locking system."""
12
-
13
11
  enabled: bool = True
14
- timeout_seconds: float = 600.0 # 10 minutes default
15
- stale_lock_hours: float = 2.0 # Clean locks older than 2 hours
12
+ timeout_seconds: float = 600.0
13
+ stale_lock_hours: float = 2.0
16
14
  lock_directory: Path = field(
17
15
  default_factory=lambda: Path.home() / ".crackerjack" / "locks"
18
16
  )
19
- session_heartbeat_interval: float = 30.0 # Heartbeat every 30s
17
+ session_heartbeat_interval: float = 30.0
20
18
  max_retry_attempts: int = 3
21
19
  retry_delay_seconds: float = 5.0
22
20
  enable_lock_monitoring: bool = True
23
21
 
24
22
  @classmethod
25
- def from_options(cls, options: "OptionsProtocol") -> "GlobalLockConfig":
26
- """Create GlobalLockConfig from CLI options.
27
-
28
- Args:
29
- options: Options object containing CLI arguments
30
-
31
- Returns:
32
- Configured GlobalLockConfig instance
33
- """
34
- # Import here to avoid circular import
35
-
36
- # Create config with custom values from options
23
+ def from_options(
24
+ cls: type["GlobalLockConfig"], options: "OptionsProtocol"
25
+ ) -> "GlobalLockConfig":
37
26
  enabled = not options.disable_global_locks
38
27
  timeout_seconds = float(options.global_lock_timeout)
39
- # Get default lock directory from field definition
28
+
40
29
  default_lock_dir = Path.home() / ".crackerjack" / "locks"
41
30
  lock_directory = (
42
31
  Path(options.global_lock_dir)
@@ -44,7 +33,6 @@ class GlobalLockConfig:
44
33
  else default_lock_dir
45
34
  )
46
35
 
47
- # Create instance with all parameters so __post_init__ is called
48
36
  config = cls(
49
37
  enabled=enabled,
50
38
  timeout_seconds=timeout_seconds,
@@ -54,56 +42,28 @@ class GlobalLockConfig:
54
42
  return config
55
43
 
56
44
  def __post_init__(self) -> None:
57
- """Ensure lock directory exists and has proper permissions."""
58
45
  self.lock_directory.mkdir(parents=True, exist_ok=True)
59
- # Set restrictive permissions (owner read/write only)
46
+
60
47
  self.lock_directory.chmod(0o700)
61
48
 
62
49
  @property
63
50
  def hostname(self) -> str:
64
- """Get the current hostname for lock identification."""
65
51
  return socket.gethostname()
66
52
 
67
53
  @property
68
54
  def session_id(self) -> str:
69
- """Get a unique session identifier."""
70
55
  return f"{self.hostname}_{os.getpid()}"
71
56
 
72
57
  def get_lock_path(self, hook_name: str) -> Path:
73
- """Get the lock file path for a specific hook.
74
-
75
- Args:
76
- hook_name: Name of the hook
77
-
78
- Returns:
79
- Path to the lock file
80
- """
81
58
  return self.lock_directory / f"{hook_name}.lock"
82
59
 
83
60
  def get_heartbeat_path(self, hook_name: str) -> Path:
84
- """Get the heartbeat file path for a specific hook.
85
-
86
- Args:
87
- hook_name: Name of the hook
88
-
89
- Returns:
90
- Path to the heartbeat file
91
- """
92
61
  return self.lock_directory / f"{hook_name}.heartbeat"
93
62
 
94
63
  def is_valid_lock_file(self, lock_path: Path) -> bool:
95
- """Check if a lock file is valid (not stale).
96
-
97
- Args:
98
- lock_path: Path to the lock file
99
-
100
- Returns:
101
- True if the lock file is valid and not stale
102
- """
103
64
  if not lock_path.exists():
104
65
  return False
105
66
 
106
- # Check if the lock file is older than stale_lock_hours
107
67
  import time
108
68
 
109
69
  file_age_hours = (time.time() - lock_path.stat().st_mtime) / 3600