crackerjack 0.31.18__py3-none-any.whl → 0.32.0__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.

@@ -468,19 +468,16 @@ class RefactoringAgent(SubAgent):
468
468
 
469
469
  def _apply_enhanced_complexity_patterns(self, content: str) -> str:
470
470
  """Apply enhanced complexity reduction patterns using SAFE_PATTERNS."""
471
- modified_content = content
472
-
473
- # Extract nested conditions to helper methods
474
- modified_content = self._extract_nested_conditions(modified_content)
475
-
476
- # Replace complex boolean expressions with helper functions
477
- modified_content = self._simplify_boolean_expressions(modified_content)
478
-
479
- # Extract validation patterns to separate methods
480
- modified_content = self._extract_validation_patterns(modified_content)
471
+ operations = [
472
+ self._extract_nested_conditions,
473
+ self._simplify_boolean_expressions,
474
+ self._extract_validation_patterns,
475
+ self._simplify_data_structures,
476
+ ]
481
477
 
482
- # Break down large dictionary/list operations
483
- modified_content = self._simplify_data_structures(modified_content)
478
+ modified_content = content
479
+ for operation in operations:
480
+ modified_content = operation(modified_content)
484
481
 
485
482
  return modified_content
486
483
 
@@ -897,13 +894,7 @@ class RefactoringAgent(SubAgent):
897
894
  def _remove_dead_code_items(self, content: str, analysis: dict[str, t.Any]) -> str:
898
895
  """Enhanced removal of dead code items from content."""
899
896
  lines = content.split("\n")
900
- lines_to_remove = self._find_lines_to_remove(lines, analysis)
901
-
902
- # Also remove unreachable code blocks
903
- lines_to_remove.update(self._find_unreachable_lines(lines, analysis))
904
-
905
- # Remove redundant code patterns
906
- lines_to_remove.update(self._find_redundant_lines(lines, analysis))
897
+ lines_to_remove = self._collect_all_removable_lines(lines, analysis)
907
898
 
908
899
  filtered_lines = [
909
900
  line for i, line in enumerate(lines) if i not in lines_to_remove
@@ -911,6 +902,22 @@ class RefactoringAgent(SubAgent):
911
902
 
912
903
  return "\n".join(filtered_lines)
913
904
 
905
+ def _collect_all_removable_lines(
906
+ self, lines: list[str], analysis: dict[str, t.Any]
907
+ ) -> set[int]:
908
+ """Collect all line indices that should be removed."""
909
+ removal_functions = [
910
+ lambda: self._find_lines_to_remove(lines, analysis),
911
+ lambda: self._find_unreachable_lines(lines, analysis),
912
+ lambda: self._find_redundant_lines(lines, analysis),
913
+ ]
914
+
915
+ lines_to_remove: set[int] = set()
916
+ for removal_func in removal_functions:
917
+ lines_to_remove.update(removal_func())
918
+
919
+ return lines_to_remove
920
+
914
921
  def _find_unreachable_lines(
915
922
  self, lines: list[str], analysis: dict[str, t.Any]
916
923
  ) -> set[int]:
@@ -933,21 +940,30 @@ class RefactoringAgent(SubAgent):
933
940
  lines_to_remove: set[int] = set()
934
941
 
935
942
  # Look for empty except blocks
936
- for i, line in enumerate(lines):
937
- stripped = line.strip()
938
- if stripped == "except:" or stripped.startswith("except "):
939
- # Check if next non-empty line is just 'pass'
940
- for j in range(i + 1, min(i + 5, len(lines))):
941
- next_line = lines[j].strip()
942
- if not next_line:
943
- continue
944
- if next_line == "pass":
945
- lines_to_remove.add(j)
946
- break
947
- break
943
+ for i in range(len(lines)):
944
+ if self._is_empty_except_block(lines, i):
945
+ empty_pass_idx = self._find_empty_pass_line(lines, i)
946
+ if empty_pass_idx is not None:
947
+ lines_to_remove.add(empty_pass_idx)
948
948
 
949
949
  return lines_to_remove
950
950
 
951
+ def _is_empty_except_block(self, lines: list[str], line_idx: int) -> bool:
952
+ """Check if line is an empty except block."""
953
+ stripped = lines[line_idx].strip()
954
+ return stripped == "except:" or stripped.startswith("except ")
955
+
956
+ def _find_empty_pass_line(self, lines: list[str], except_idx: int) -> int | None:
957
+ """Find the pass line in an empty except block."""
958
+ for j in range(except_idx + 1, min(except_idx + 5, len(lines))):
959
+ next_line = lines[j].strip()
960
+ if not next_line:
961
+ continue
962
+ if next_line == "pass":
963
+ return j
964
+ break
965
+ return None
966
+
951
967
  def _extract_function_content(
952
968
  self, lines: list[str], func_info: dict[str, t.Any]
953
969
  ) -> str:
@@ -968,30 +984,35 @@ class RefactoringAgent(SubAgent):
968
984
  ) -> str:
969
985
  """Apply function extraction by replacing original with calls to helpers."""
970
986
  lines = content.split("\n")
971
- validation_result = self._validate_extraction_params(
972
- lines, func_info, extracted_helpers
973
- )
974
- if validation_result:
975
- return validation_result
976
987
 
977
- new_lines = self._replace_function_with_calls(
978
- lines, func_info, extracted_helpers
979
- )
980
- return self._add_helper_definitions(new_lines, func_info, extracted_helpers)
988
+ if not self._is_extraction_valid(lines, func_info, extracted_helpers):
989
+ return "\n".join(lines)
990
+
991
+ return self._perform_extraction(lines, func_info, extracted_helpers)
981
992
 
982
- def _validate_extraction_params(
993
+ def _is_extraction_valid(
983
994
  self,
984
995
  lines: list[str],
985
996
  func_info: dict[str, t.Any],
986
997
  extracted_helpers: list[dict[str, str]],
987
- ) -> str | None:
988
- """Validate parameters for function extraction."""
998
+ ) -> bool:
999
+ """Check if extraction parameters are valid."""
989
1000
  start_line = func_info["line_start"] - 1
990
1001
  end_line = func_info.get("line_end", len(lines)) - 1
991
1002
 
992
- if not extracted_helpers or start_line < 0 or end_line >= len(lines):
993
- return "\n".join(lines)
994
- return None
1003
+ return bool(extracted_helpers) and start_line >= 0 and end_line < len(lines)
1004
+
1005
+ def _perform_extraction(
1006
+ self,
1007
+ lines: list[str],
1008
+ func_info: dict[str, t.Any],
1009
+ extracted_helpers: list[dict[str, str]],
1010
+ ) -> str:
1011
+ """Perform the actual function extraction."""
1012
+ new_lines = self._replace_function_with_calls(
1013
+ lines, func_info, extracted_helpers
1014
+ )
1015
+ return self._add_helper_definitions(new_lines, func_info, extracted_helpers)
995
1016
 
996
1017
  def _replace_function_with_calls(
997
1018
  self,
@@ -8,19 +8,19 @@ from rich.console import Console
8
8
  from .options import Options
9
9
 
10
10
 
11
- def setup_ai_agent_env(ai_agent: bool, verbose: bool = False) -> None:
12
- # Set debug environment variable if verbose is enabled
13
- if verbose:
11
+ def setup_ai_agent_env(ai_agent: bool, debug_mode: bool = False) -> None:
12
+ # Only set debug environment variable if debug mode is explicitly enabled
13
+ if debug_mode:
14
14
  os.environ["CRACKERJACK_DEBUG"] = "1"
15
15
 
16
16
  if ai_agent:
17
17
  os.environ["AI_AGENT"] = "1"
18
- os.environ["AI_AGENT_DEBUG"] = "1"
19
-
20
- if verbose:
18
+ # Only enable AI agent debug if debug mode is explicitly requested
19
+ if debug_mode:
20
+ os.environ["AI_AGENT_DEBUG"] = "1"
21
21
  os.environ["AI_AGENT_VERBOSE"] = "1"
22
22
 
23
- if verbose:
23
+ # Show debug configuration when debug mode is enabled
24
24
  console = Console()
25
25
  console.print(
26
26
  "[bold cyan]🐛 AI Agent Debug Mode Configuration: [/ bold cyan]",
@@ -14,6 +14,17 @@ class RetryPolicy(Enum):
14
14
  ALL_HOOKS = "all_hooks"
15
15
 
16
16
 
17
+ class SecurityLevel(Enum):
18
+ """Security classification for hooks - determines bypass behavior."""
19
+
20
+ CRITICAL = (
21
+ "critical" # Cannot be bypassed for publishing (security scans, type safety)
22
+ )
23
+ HIGH = "high" # Important but can be bypassed with warning
24
+ MEDIUM = "medium" # Standard checks, bypassable
25
+ LOW = "low" # Formatting/style, always bypassable
26
+
27
+
17
28
  @dataclass
18
29
  class HookDefinition:
19
30
  name: str
@@ -24,6 +35,7 @@ class HookDefinition:
24
35
  is_formatting: bool = False
25
36
  manual_stage: bool = False
26
37
  config_path: Path | None = None
38
+ security_level: SecurityLevel = SecurityLevel.MEDIUM # Default security level
27
39
 
28
40
  def get_command(self) -> list[str]:
29
41
  # Use direct pre-commit execution (pre-commit manages its own environments)
@@ -66,60 +78,72 @@ FAST_HOOKS = [
66
78
  is_formatting=True, # Treat as formatting for autofix purposes
67
79
  timeout=30,
68
80
  retry_on_failure=True, # Enable retries for autofix
81
+ security_level=SecurityLevel.HIGH, # Regex vulnerabilities are security-relevant
69
82
  ),
70
83
  HookDefinition(
71
84
  name="trailing-whitespace",
72
85
  command=[], # Dynamically built by get_command()
73
86
  is_formatting=True,
74
87
  retry_on_failure=True,
88
+ security_level=SecurityLevel.LOW, # Whitespace formatting, always bypassable
75
89
  ),
76
90
  HookDefinition(
77
91
  name="end-of-file-fixer",
78
92
  command=[], # Dynamically built by get_command()
79
93
  is_formatting=True,
80
94
  retry_on_failure=True,
95
+ security_level=SecurityLevel.LOW, # File formatting, always bypassable
81
96
  ),
82
97
  HookDefinition(
83
98
  name="check-yaml",
84
99
  command=[], # Dynamically built by get_command()
100
+ security_level=SecurityLevel.MEDIUM, # File validation, standard check
85
101
  ),
86
102
  HookDefinition(
87
103
  name="check-toml",
88
104
  command=[], # Dynamically built by get_command()
105
+ security_level=SecurityLevel.MEDIUM, # File validation, standard check
89
106
  ),
90
107
  HookDefinition(
91
108
  name="check-added-large-files",
92
109
  command=[], # Dynamically built by get_command()
110
+ security_level=SecurityLevel.HIGH, # Large files can be security risk
93
111
  ),
94
112
  HookDefinition(
95
113
  name="uv-lock",
96
114
  command=[], # Dynamically built by get_command()
115
+ security_level=SecurityLevel.HIGH, # Dependency locking is security-relevant
97
116
  ),
98
117
  HookDefinition(
99
118
  name="gitleaks",
100
119
  command=[], # Dynamically built by get_command()
120
+ security_level=SecurityLevel.CRITICAL, # Secret detection is critical for security
101
121
  ),
102
122
  HookDefinition(
103
123
  name="codespell",
104
124
  command=[], # Dynamically built by get_command()
125
+ security_level=SecurityLevel.LOW, # Spelling, not security-critical
105
126
  ),
106
127
  HookDefinition(
107
128
  name="ruff-check",
108
129
  command=[], # Dynamically built by get_command()
109
130
  is_formatting=True, # Treat as formatting for autofix purposes
110
131
  retry_on_failure=True, # Enable retries for autofix
132
+ security_level=SecurityLevel.MEDIUM, # Code quality, not directly security
111
133
  ),
112
134
  HookDefinition(
113
135
  name="ruff-format",
114
136
  command=[], # Dynamically built by get_command()
115
137
  is_formatting=True,
116
138
  retry_on_failure=True,
139
+ security_level=SecurityLevel.LOW, # Pure formatting, always bypassable
117
140
  ),
118
141
  HookDefinition(
119
142
  name="mdformat",
120
143
  command=[], # Dynamically built by get_command()
121
144
  is_formatting=True,
122
145
  retry_on_failure=True,
146
+ security_level=SecurityLevel.LOW, # Documentation formatting, always bypassable
123
147
  ),
124
148
  ]
125
149
 
@@ -127,37 +151,42 @@ COMPREHENSIVE_HOOKS = [
127
151
  HookDefinition(
128
152
  name="pyright",
129
153
  command=[], # Dynamically built by get_command()
130
- timeout=120,
154
+ timeout=300, # Fixed: Use 300s to match pytest config
131
155
  stage=HookStage.COMPREHENSIVE,
132
156
  manual_stage=True,
157
+ security_level=SecurityLevel.CRITICAL, # Type safety prevents security holes
133
158
  ),
134
159
  HookDefinition(
135
160
  name="bandit",
136
161
  command=[], # Dynamically built by get_command()
137
- timeout=120,
162
+ timeout=300, # Fixed: Use 300s to match pytest config
138
163
  stage=HookStage.COMPREHENSIVE,
139
164
  manual_stage=True,
165
+ security_level=SecurityLevel.CRITICAL, # Security vulnerability scanning
140
166
  ),
141
167
  HookDefinition(
142
168
  name="vulture",
143
169
  command=[], # Dynamically built by get_command()
144
- timeout=120,
170
+ timeout=300, # Fixed: Use 300s to match pytest config
145
171
  stage=HookStage.COMPREHENSIVE,
146
172
  manual_stage=True,
173
+ security_level=SecurityLevel.MEDIUM, # Dead code removal, not critical for security
147
174
  ),
148
175
  HookDefinition(
149
176
  name="refurb",
150
177
  command=[], # Dynamically built by get_command()
151
- timeout=120,
178
+ timeout=300, # Fixed: Use 300s to match pytest config
152
179
  stage=HookStage.COMPREHENSIVE,
153
180
  manual_stage=True,
181
+ security_level=SecurityLevel.MEDIUM, # Code quality, not directly security
154
182
  ),
155
183
  HookDefinition(
156
184
  name="creosote",
157
185
  command=[], # Dynamically built by get_command()
158
- timeout=120,
186
+ timeout=300, # Fixed: Use 300s to match pytest config
159
187
  stage=HookStage.COMPREHENSIVE,
160
188
  manual_stage=True,
189
+ security_level=SecurityLevel.HIGH, # Dependency analysis, security-relevant
161
190
  ),
162
191
  HookDefinition(
163
192
  name="complexipy",
@@ -165,6 +194,7 @@ COMPREHENSIVE_HOOKS = [
165
194
  timeout=60,
166
195
  stage=HookStage.COMPREHENSIVE,
167
196
  manual_stage=True,
197
+ security_level=SecurityLevel.MEDIUM, # Complexity analysis, not directly security
168
198
  ),
169
199
  ]
170
200
 
@@ -179,7 +209,7 @@ FAST_STRATEGY = HookStrategy(
179
209
  COMPREHENSIVE_STRATEGY = HookStrategy(
180
210
  name="comprehensive",
181
211
  hooks=COMPREHENSIVE_HOOKS,
182
- timeout=120,
212
+ timeout=300, # Fixed: Use 300s to match pytest config
183
213
  retry_policy=RetryPolicy.NONE,
184
214
  )
185
215
 
@@ -676,8 +676,8 @@ class AsyncWorkflowOrchestrator:
676
676
  session_id = getattr(self, "web_job_id", None) or str(int(time.time()))[:8]
677
677
  debug_log_file = log_manager.create_debug_log_file(session_id)
678
678
 
679
- # Set log level based on verbosity - DEBUG only in verbose or debug mode
680
- log_level = "DEBUG" if (self.verbose or self.debug) else "INFO"
679
+ # Set log level based on debug flag only - verbose should not enable DEBUG logs
680
+ log_level = "DEBUG" if self.debug else "INFO"
681
681
  setup_structured_logging(
682
682
  level=log_level, json_output=False, log_file=debug_log_file
683
683
  )
@@ -445,6 +445,13 @@ class PhaseCoordinator:
445
445
  ) -> bool:
446
446
  new_version = self.publish_manager.bump_version(version_type)
447
447
 
448
+ # Stage all changes after version bumping and code cleaning (if enabled)
449
+ self.console.print("[blue]📂[/ blue] Staging all changes for publishing...")
450
+ if not self.git_service.add_all_files():
451
+ self.console.print(
452
+ "[yellow]⚠️[/ yellow] Failed to stage files, continuing with publish..."
453
+ )
454
+
448
455
  if not options.no_git_tags:
449
456
  self.publish_manager.create_git_tag(new_version)
450
457
 
@@ -483,6 +490,27 @@ class PhaseCoordinator:
483
490
 
484
491
  def _handle_no_changes_to_commit(self) -> bool:
485
492
  self.console.print("[yellow]ℹ️[/ yellow] No changes to commit")
493
+
494
+ # Check if there are unpushed commits
495
+ from contextlib import suppress
496
+
497
+ with suppress(ValueError, Exception):
498
+ commit_count = self.git_service.get_unpushed_commit_count()
499
+ if commit_count > 0:
500
+ self.console.print(
501
+ f"[blue]📤[/ blue] Found {commit_count} unpushed commit(s), attempting push..."
502
+ )
503
+ if self.git_service.push():
504
+ self.session.complete_task(
505
+ "commit",
506
+ f"No new changes, pushed {commit_count} existing commit(s)",
507
+ )
508
+ return True
509
+ else:
510
+ self.console.print(
511
+ "[yellow]⚠️[/ yellow] Push failed for existing commits"
512
+ )
513
+
486
514
  self.session.complete_task("commit", "No changes to commit")
487
515
  return True
488
516