crackerjack 0.32.0__py3-none-any.whl → 0.33.1__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 (200) 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 +64 -6
  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 +257 -218
  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 +558 -240
  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 +66 -13
  74. crackerjack/managers/test_command_builder.py +5 -17
  75. crackerjack/managers/test_executor.py +1 -3
  76. crackerjack/managers/test_manager.py +109 -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 +161 -32
  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 +174 -33
  92. crackerjack/mcp/tools/error_analyzer.py +3 -2
  93. crackerjack/mcp/tools/execution_tools.py +15 -12
  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 +3 -0
  109. crackerjack/mixins/error_handling.py +145 -0
  110. crackerjack/models/config.py +21 -1
  111. crackerjack/models/config_adapter.py +49 -1
  112. crackerjack/models/protocols.py +176 -107
  113. crackerjack/models/resource_protocols.py +55 -210
  114. crackerjack/models/task.py +3 -0
  115. crackerjack/monitoring/ai_agent_watchdog.py +13 -13
  116. crackerjack/monitoring/metrics_collector.py +426 -0
  117. crackerjack/monitoring/regression_prevention.py +8 -8
  118. crackerjack/monitoring/websocket_server.py +643 -0
  119. crackerjack/orchestration/advanced_orchestrator.py +11 -6
  120. crackerjack/orchestration/coverage_improvement.py +3 -3
  121. crackerjack/orchestration/execution_strategies.py +26 -6
  122. crackerjack/orchestration/test_progress_streamer.py +8 -5
  123. crackerjack/plugins/base.py +2 -2
  124. crackerjack/plugins/hooks.py +7 -0
  125. crackerjack/plugins/managers.py +11 -8
  126. crackerjack/security/__init__.py +0 -1
  127. crackerjack/security/audit.py +90 -105
  128. crackerjack/services/anomaly_detector.py +392 -0
  129. crackerjack/services/api_extractor.py +615 -0
  130. crackerjack/services/backup_service.py +2 -2
  131. crackerjack/services/bounded_status_operations.py +15 -152
  132. crackerjack/services/cache.py +127 -1
  133. crackerjack/services/changelog_automation.py +395 -0
  134. crackerjack/services/config.py +18 -11
  135. crackerjack/services/config_merge.py +30 -85
  136. crackerjack/services/config_template.py +506 -0
  137. crackerjack/services/contextual_ai_assistant.py +48 -22
  138. crackerjack/services/coverage_badge_service.py +171 -0
  139. crackerjack/services/coverage_ratchet.py +41 -17
  140. crackerjack/services/debug.py +3 -3
  141. crackerjack/services/dependency_analyzer.py +460 -0
  142. crackerjack/services/dependency_monitor.py +14 -11
  143. crackerjack/services/documentation_generator.py +491 -0
  144. crackerjack/services/documentation_service.py +675 -0
  145. crackerjack/services/enhanced_filesystem.py +6 -5
  146. crackerjack/services/enterprise_optimizer.py +865 -0
  147. crackerjack/services/error_pattern_analyzer.py +676 -0
  148. crackerjack/services/file_hasher.py +1 -1
  149. crackerjack/services/git.py +41 -45
  150. crackerjack/services/health_metrics.py +10 -8
  151. crackerjack/services/heatmap_generator.py +735 -0
  152. crackerjack/services/initialization.py +30 -33
  153. crackerjack/services/input_validator.py +5 -97
  154. crackerjack/services/intelligent_commit.py +327 -0
  155. crackerjack/services/log_manager.py +15 -12
  156. crackerjack/services/logging.py +4 -3
  157. crackerjack/services/lsp_client.py +628 -0
  158. crackerjack/services/memory_optimizer.py +409 -0
  159. crackerjack/services/metrics.py +42 -33
  160. crackerjack/services/parallel_executor.py +416 -0
  161. crackerjack/services/pattern_cache.py +1 -1
  162. crackerjack/services/pattern_detector.py +6 -6
  163. crackerjack/services/performance_benchmarks.py +250 -576
  164. crackerjack/services/performance_cache.py +382 -0
  165. crackerjack/services/performance_monitor.py +565 -0
  166. crackerjack/services/predictive_analytics.py +510 -0
  167. crackerjack/services/quality_baseline.py +234 -0
  168. crackerjack/services/quality_baseline_enhanced.py +646 -0
  169. crackerjack/services/quality_intelligence.py +785 -0
  170. crackerjack/services/regex_patterns.py +605 -524
  171. crackerjack/services/regex_utils.py +43 -123
  172. crackerjack/services/secure_path_utils.py +5 -164
  173. crackerjack/services/secure_status_formatter.py +30 -141
  174. crackerjack/services/secure_subprocess.py +11 -92
  175. crackerjack/services/security.py +61 -30
  176. crackerjack/services/security_logger.py +18 -22
  177. crackerjack/services/server_manager.py +124 -16
  178. crackerjack/services/status_authentication.py +16 -159
  179. crackerjack/services/status_security_manager.py +4 -131
  180. crackerjack/services/terminal_utils.py +0 -0
  181. crackerjack/services/thread_safe_status_collector.py +19 -125
  182. crackerjack/services/unified_config.py +21 -13
  183. crackerjack/services/validation_rate_limiter.py +5 -54
  184. crackerjack/services/version_analyzer.py +459 -0
  185. crackerjack/services/version_checker.py +1 -1
  186. crackerjack/services/websocket_resource_limiter.py +10 -144
  187. crackerjack/services/zuban_lsp_service.py +390 -0
  188. crackerjack/slash_commands/__init__.py +2 -7
  189. crackerjack/slash_commands/run.md +2 -2
  190. crackerjack/tools/validate_input_validator_patterns.py +14 -40
  191. crackerjack/tools/validate_regex_patterns.py +19 -48
  192. {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.dist-info}/METADATA +197 -26
  193. crackerjack-0.33.1.dist-info/RECORD +229 -0
  194. crackerjack/CLAUDE.md +0 -207
  195. crackerjack/RULES.md +0 -380
  196. crackerjack/py313.py +0 -234
  197. crackerjack-0.32.0.dist-info/RECORD +0 -180
  198. {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.dist-info}/WHEEL +0 -0
  199. {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.dist-info}/entry_points.txt +0 -0
  200. {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.dist-info}/licenses/LICENSE +0 -0
@@ -20,7 +20,7 @@ class CoverageImprovementOrchestrator:
20
20
  ) -> bool:
21
21
  try:
22
22
  if current_coverage is None:
23
- coverage_status = self.coverage_service.get_status_report()
23
+ coverage_status = self.coverage_service.get_ratchet_data()
24
24
  current_coverage = coverage_status.get("current_coverage", 0.0)
25
25
 
26
26
  if current_coverage is not None and current_coverage < 100.0:
@@ -42,7 +42,7 @@ class CoverageImprovementOrchestrator:
42
42
  ) -> Issue:
43
43
  if coverage_gap is None:
44
44
  try:
45
- coverage_status = self.coverage_service.get_status_report()
45
+ coverage_status = self.coverage_service.get_ratchet_data()
46
46
  current_coverage = coverage_status.get("current_coverage", 0.0)
47
47
  coverage_gap = 100.0 - current_coverage
48
48
  except Exception:
@@ -150,7 +150,7 @@ class CoverageImprovementOrchestrator:
150
150
  from contextlib import suppress
151
151
 
152
152
  with suppress(Exception):
153
- coverage_status = self.coverage_service.get_status_report()
153
+ coverage_status = self.coverage_service.get_ratchet_data()
154
154
  current_coverage = coverage_status.get("current_coverage", 0.0)
155
155
 
156
156
  if current_coverage < 25.0:
@@ -95,8 +95,8 @@ class ExecutionContext:
95
95
  self.changed_files = changed_files or []
96
96
  self.iteration_count = iteration_count
97
97
 
98
- self.total_python_files = len(list(pkg_path.rglob(" * .py")))
99
- self.total_test_files = len(list(pkg_path.glob("tests / test_ * .py")))
98
+ self.total_python_files = len(list[t.Any](pkg_path.rglob(" * .py")))
99
+ self.total_test_files = len(list[t.Any](pkg_path.glob("tests / test_ * .py")))
100
100
  self.has_complex_setup = self._detect_complex_setup()
101
101
  self.estimated_hook_duration = self._estimate_hook_duration()
102
102
 
@@ -105,8 +105,8 @@ class ExecutionContext:
105
105
  (self.pkg_path / "pyproject.toml").exists(),
106
106
  (self.pkg_path / "setup.py").exists(),
107
107
  (self.pkg_path / "requirements.txt").exists(),
108
- len(list(self.pkg_path.rglob(" * .py"))) > 50,
109
- len(list(self.pkg_path.glob("tests / test_ * .py"))) > 20,
108
+ len(list[t.Any](self.pkg_path.rglob(" * .py"))) > 50,
109
+ len(list[t.Any](self.pkg_path.glob("tests / test_ * .py"))) > 20,
110
110
  ]
111
111
  return sum(complex_indicators) >= 3
112
112
 
@@ -117,6 +117,26 @@ class ExecutionContext:
117
117
 
118
118
  return base_time + file_factor + test_factor
119
119
 
120
+ @property
121
+ def ai_agent_mode(self) -> bool:
122
+ """Check if AI agent mode is enabled."""
123
+ return getattr(self.options, "ai_agent", False)
124
+
125
+ @property
126
+ def ai_debug_mode(self) -> bool:
127
+ """Check if AI debug mode is enabled."""
128
+ return getattr(self.options, "ai_debug", False)
129
+
130
+ @property
131
+ def interactive(self) -> bool:
132
+ """Check if interactive mode is enabled."""
133
+ return getattr(self.options, "interactive", False)
134
+
135
+ @property
136
+ def working_directory(self) -> Path:
137
+ """Get the working directory path."""
138
+ return self.pkg_path
139
+
120
140
 
121
141
  class StrategySelector:
122
142
  def __init__(self, console: Console) -> None:
@@ -169,7 +189,7 @@ class StrategySelector:
169
189
  strategy: HookStrategy,
170
190
  context: ExecutionContext,
171
191
  ) -> HookStrategy:
172
- priority_hooks = set(context.previous_failures)
192
+ priority_hooks = set[t.Any](context.previous_failures)
173
193
 
174
194
  if context.changed_files:
175
195
  for file_path in context.changed_files:
@@ -243,7 +263,7 @@ class OrchestrationPlanner:
243
263
  test_plan=test_plan,
244
264
  ai_plan=ai_plan,
245
265
  estimated_total_duration=sum(
246
- plan["estimated_duration"] for plan in hook_plans
266
+ int(plan["estimated_duration"]) for plan in hook_plans
247
267
  )
248
268
  + test_plan["estimated_duration"],
249
269
  )
@@ -109,7 +109,6 @@ class PytestOutputParser:
109
109
  self.test_traceback_buffer: list[str] = []
110
110
  self.in_traceback = False
111
111
 
112
- # Cache compiled patterns for optimal performance during test parsing
113
112
  self._test_start_pattern = CompiledPatternCache.get_compiled_pattern(
114
113
  SAFE_PATTERNS["pytest_test_start"].pattern
115
114
  )
@@ -144,7 +143,7 @@ class PytestOutputParser:
144
143
  self._process_current_test_line(line, suite_info)
145
144
 
146
145
  return {
147
- "tests": list(tests.values()),
146
+ "tests": list[t.Any](tests.values()),
148
147
  "suite_progress": suite_info,
149
148
  "test_count": len(tests),
150
149
  }
@@ -155,6 +154,7 @@ class PytestOutputParser:
155
154
  suite_info: TestSuiteProgress,
156
155
  ) -> None:
157
156
  if match := self._test_collection_pattern.search(line):
157
+ assert match is not None # Type checker: match cannot be None here
158
158
  suite_info.total_tests = int(match.group(1))
159
159
 
160
160
  def _process_test_result_line(
@@ -164,6 +164,7 @@ class PytestOutputParser:
164
164
  suite_info: TestSuiteProgress,
165
165
  ) -> None:
166
166
  if match := self._detailed_test_pattern.match(line):
167
+ assert match is not None # Type checker: match cannot be None here
167
168
  file_path, test_name, status = match.groups()
168
169
  test_id = f"{file_path}:: {test_name}"
169
170
 
@@ -212,6 +213,7 @@ class PytestOutputParser:
212
213
 
213
214
  def _process_coverage_line(self, line: str, suite_info: TestSuiteProgress) -> None:
214
215
  if match := self._coverage_pattern.search(line):
216
+ assert match is not None # Type checker: match cannot be None here
215
217
  suite_info.coverage_percentage = float(match.group(1))
216
218
 
217
219
  def _process_current_test_line(
@@ -225,9 +227,9 @@ class PytestOutputParser:
225
227
  suite_info.current_test = line.split()[0] if line.split() else None
226
228
 
227
229
  def parse_test_failure_details(self, output_lines: list[str]) -> dict[str, str]:
228
- failures = {}
229
- current_test = None
230
- current_traceback = []
230
+ failures: dict[str, str] = {}
231
+ current_test: str | None = None
232
+ current_traceback: list[str] = []
231
233
  in_failure_section = False
232
234
 
233
235
  for line in output_lines:
@@ -531,6 +533,7 @@ class TestProgressStreamer:
531
533
  suite_progress.current_test = parts[0]
532
534
 
533
535
  if match := self.parser._test_collection_pattern.search(line):
536
+ assert match is not None # Type checker: match cannot be None here
534
537
  suite_progress.total_tests = int(match.group(1))
535
538
 
536
539
  if "PASSED" in line:
@@ -24,7 +24,7 @@ class PluginMetadata:
24
24
  requires_python: str = "> = 3.11"
25
25
  dependencies: list[str] = field(default_factory=list)
26
26
  entry_point: str = ""
27
- config_schema: dict[str, t.Any] = field(default_factory=dict)
27
+ config_schema: dict[str, t.Any] = field(default_factory=dict[str, t.Any])
28
28
 
29
29
  def to_dict(self) -> dict[str, t.Any]:
30
30
  return {
@@ -148,7 +148,7 @@ class PluginRegistry:
148
148
  if plugin_type:
149
149
  plugins = self.get_by_type(plugin_type)
150
150
  else:
151
- plugins = list(self._plugins.values())
151
+ plugins = list[t.Any](self._plugins.values())
152
152
 
153
153
  return [p for p in plugins if p.enabled]
154
154
 
@@ -129,6 +129,13 @@ class CustomHookPlugin(HookPluginBase):
129
129
  start_time = time.time()
130
130
 
131
131
  try:
132
+ if hook_def.command is None:
133
+ return HookResult(
134
+ name=hook_def.name,
135
+ status="failed",
136
+ message="Hook command is None",
137
+ duration=0.0,
138
+ )
132
139
  cmd = hook_def.command.copy()
133
140
  if hook_def.requires_files and files:
134
141
  cmd.extend(str(f) for f in files)
@@ -7,7 +7,7 @@ from rich.console import Console
7
7
  from crackerjack.models.protocols import OptionsProtocol
8
8
 
9
9
  from .base import PluginRegistry, PluginType, get_plugin_registry
10
- from .hooks import HookPluginRegistry, get_hook_plugin_registry
10
+ from .hooks import HookPluginBase, HookPluginRegistry, get_hook_plugin_registry
11
11
  from .loader import PluginDiscovery, PluginLoader
12
12
 
13
13
 
@@ -59,9 +59,10 @@ class PluginManager:
59
59
 
60
60
  hook_plugins = self.registry.get_enabled(PluginType.HOOK)
61
61
  for plugin in hook_plugins:
62
- if hasattr(plugin, "initialize"):
63
- plugin.initialize(self.console, self.project_path)
64
- self.hook_registry.register_hook_plugin(plugin)
62
+ if isinstance(plugin, HookPluginBase):
63
+ if hasattr(plugin, "initialize"):
64
+ plugin.initialize(self.console, self.project_path)
65
+ self.hook_registry.register_hook_plugin(plugin)
65
66
 
66
67
  self._initialized = True
67
68
  return True
@@ -97,7 +98,7 @@ class PluginManager:
97
98
  if plugin_type:
98
99
  plugins = self.registry.get_by_type(plugin_type)
99
100
  else:
100
- plugins = list(self.registry.list_all().values())
101
+ plugins = list[t.Any](self.registry.list_all().values())
101
102
 
102
103
  plugin_info = []
103
104
  for plugin in plugins:
@@ -120,7 +121,7 @@ class PluginManager:
120
121
  stats["hook_plugins"] = {
121
122
  "active_plugins": len(hook_plugins),
122
123
  "total_custom_hooks": len(custom_hooks),
123
- "hook_names": list(custom_hooks.keys()),
124
+ "hook_names": list[t.Any](custom_hooks.keys()),
124
125
  }
125
126
 
126
127
  return stats
@@ -144,7 +145,9 @@ class PluginManager:
144
145
  if success:
145
146
  self.console.print(f"[green]✅[/ green] Enabled plugin: {plugin_name}")
146
147
 
147
- if plugin.plugin_type == PluginType.HOOK:
148
+ if plugin.plugin_type == PluginType.HOOK and isinstance(
149
+ plugin, HookPluginBase
150
+ ):
148
151
  self.hook_registry.register_hook_plugin(plugin)
149
152
 
150
153
  return True
@@ -250,7 +253,7 @@ class PluginManager:
250
253
 
251
254
  def get_available_custom_hooks(self) -> list[str]:
252
255
  custom_hooks = self.hook_registry.get_all_custom_hooks()
253
- return list(custom_hooks.keys())
256
+ return list[t.Any](custom_hooks.keys())
254
257
 
255
258
  def execute_custom_hook(
256
259
  self,
@@ -1 +0,0 @@
1
- """Security utilities for Crackerjack."""
@@ -1,16 +1,11 @@
1
- """Security audit utilities for secure SDLC practices."""
2
-
3
1
  import typing as t
4
2
  from dataclasses import dataclass
5
- from enum import Enum
6
3
 
7
4
  from crackerjack.config.hooks import SecurityLevel
8
5
 
9
6
 
10
7
  @dataclass
11
8
  class SecurityCheckResult:
12
- """Result of a security check."""
13
-
14
9
  hook_name: str
15
10
  security_level: SecurityLevel
16
11
  passed: bool
@@ -18,74 +13,55 @@ class SecurityCheckResult:
18
13
  details: dict[str, t.Any] | None = None
19
14
 
20
15
 
21
- @dataclass
16
+ @dataclass
22
17
  class SecurityAuditReport:
23
- """Comprehensive security audit report for publishing decisions."""
24
-
25
18
  critical_failures: list[SecurityCheckResult]
26
- high_failures: list[SecurityCheckResult]
19
+ high_failures: list[SecurityCheckResult]
27
20
  medium_failures: list[SecurityCheckResult]
28
21
  low_failures: list[SecurityCheckResult]
29
-
22
+
30
23
  allows_publishing: bool
31
24
  security_warnings: list[str]
32
25
  recommendations: list[str]
33
-
26
+
34
27
  @property
35
28
  def has_critical_failures(self) -> bool:
36
- """Check if there are any critical security failures."""
37
29
  return len(self.critical_failures) > 0
38
-
30
+
39
31
  @property
40
32
  def total_failures(self) -> int:
41
- """Get total number of failed checks."""
42
33
  return (
43
- len(self.critical_failures) +
44
- len(self.high_failures) +
45
- len(self.medium_failures) +
46
- len(self.low_failures)
34
+ len(self.critical_failures)
35
+ + len(self.high_failures)
36
+ + len(self.medium_failures)
37
+ + len(self.low_failures)
47
38
  )
48
39
 
49
40
 
50
41
  class SecurityAuditor:
51
- """Security auditor for hook results following OWASP secure SDLC practices."""
52
-
53
- # Security-critical hooks that CANNOT be bypassed for publishing
54
42
  CRITICAL_HOOKS = {
55
- 'bandit': 'Security vulnerability detection (OWASP A09)',
56
- 'pyright': 'Type safety prevents runtime security holes (OWASP A04)',
57
- 'gitleaks': 'Secret/credential detection (OWASP A07)',
43
+ "bandit": "Security vulnerability detection (OWASP A09)",
44
+ "pyright": "Type safety prevents runtime security holes (OWASP A04)",
45
+ "gitleaks": "Secret/credential detection (OWASP A07)",
58
46
  }
59
-
60
- # High-importance security hooks that can be bypassed with warnings
47
+
61
48
  HIGH_SECURITY_HOOKS = {
62
- 'validate-regex-patterns': 'Regex vulnerability detection',
63
- 'creosote': 'Dependency vulnerability analysis',
64
- 'check-added-large-files': 'Large file security analysis',
65
- 'uv-lock': 'Dependency lock security',
49
+ "validate-regex-patterns": "Regex vulnerability detection",
50
+ "creosote": "Dependency vulnerability analysis",
51
+ "check-added-large-files": "Large file security analysis",
52
+ "uv-lock": "Dependency lock security",
66
53
  }
67
-
54
+
68
55
  def audit_hook_results(
69
- self,
70
- fast_results: list[t.Any],
71
- comprehensive_results: list[t.Any]
56
+ self, fast_results: list[t.Any], comprehensive_results: list[t.Any]
72
57
  ) -> SecurityAuditReport:
73
- """Audit hook results and generate security report.
74
-
75
- Args:
76
- fast_results: Results from fast hooks
77
- comprehensive_results: Results from comprehensive hooks
78
-
79
- Returns:
80
- SecurityAuditReport with security analysis
81
- """
82
58
  all_results = fast_results + comprehensive_results
83
-
59
+
84
60
  critical_failures = []
85
- high_failures = []
61
+ high_failures = []
86
62
  medium_failures = []
87
63
  low_failures = []
88
-
64
+
89
65
  for result in all_results:
90
66
  check_result = self._analyze_hook_result(result)
91
67
  if not check_result.passed:
@@ -97,18 +73,17 @@ class SecurityAuditor:
97
73
  medium_failures.append(check_result)
98
74
  else:
99
75
  low_failures.append(check_result)
100
-
101
- # Publishing is allowed only if no critical failures exist
76
+
102
77
  allows_publishing = len(critical_failures) == 0
103
-
78
+
104
79
  security_warnings = self._generate_security_warnings(
105
80
  critical_failures, high_failures, medium_failures
106
81
  )
107
-
82
+
108
83
  recommendations = self._generate_security_recommendations(
109
84
  critical_failures, high_failures, medium_failures
110
85
  )
111
-
86
+
112
87
  return SecurityAuditReport(
113
88
  critical_failures=critical_failures,
114
89
  high_failures=high_failures,
@@ -118,95 +93,105 @@ class SecurityAuditor:
118
93
  security_warnings=security_warnings,
119
94
  recommendations=recommendations,
120
95
  )
121
-
96
+
122
97
  def _analyze_hook_result(self, result: t.Any) -> SecurityCheckResult:
123
- """Analyze a single hook result for security implications."""
124
- hook_name = getattr(result, 'name', 'unknown')
125
- is_failed = getattr(result, 'status', 'unknown') in ('failed', 'error', 'timeout')
126
- error_message = getattr(result, 'output', None) or getattr(result, 'error', None)
127
-
128
- # Determine security level
98
+ hook_name = getattr(result, "name", "unknown")
99
+ is_failed = getattr(result, "status", "unknown") in (
100
+ "failed",
101
+ "error",
102
+ "timeout",
103
+ )
104
+ error_message = getattr(result, "output", None) or getattr(
105
+ result, "error", None
106
+ )
107
+
129
108
  security_level = self._get_hook_security_level(hook_name)
130
-
109
+
131
110
  return SecurityCheckResult(
132
111
  hook_name=hook_name,
133
112
  security_level=security_level,
134
113
  passed=not is_failed,
135
114
  error_message=error_message,
136
- details={'status': getattr(result, 'status', 'unknown')},
115
+ details={"status": getattr(result, "status", "unknown")},
137
116
  )
138
-
117
+
139
118
  def _get_hook_security_level(self, hook_name: str) -> SecurityLevel:
140
- """Get security level for a hook name."""
141
119
  hook_name_lower = hook_name.lower()
142
-
143
- if hook_name_lower in [name.lower() for name in self.CRITICAL_HOOKS]:
120
+
121
+ if hook_name_lower in (name.lower() for name in self.CRITICAL_HOOKS):
144
122
  return SecurityLevel.CRITICAL
145
- elif hook_name_lower in [name.lower() for name in self.HIGH_SECURITY_HOOKS]:
123
+ elif hook_name_lower in (name.lower() for name in self.HIGH_SECURITY_HOOKS):
146
124
  return SecurityLevel.HIGH
147
- elif hook_name_lower in ['ruff-check', 'vulture', 'refurb', 'complexipy']:
125
+ elif hook_name_lower in ("ruff-check", "vulture", "refurb", "complexipy"):
148
126
  return SecurityLevel.MEDIUM
149
- else:
150
- return SecurityLevel.LOW
151
-
127
+ return SecurityLevel.LOW
128
+
152
129
  def _generate_security_warnings(
153
- self,
130
+ self,
154
131
  critical: list[SecurityCheckResult],
155
- high: list[SecurityCheckResult],
156
- medium: list[SecurityCheckResult]
132
+ high: list[SecurityCheckResult],
133
+ medium: list[SecurityCheckResult],
157
134
  ) -> list[str]:
158
- """Generate security warnings based on failed checks."""
159
135
  warnings = []
160
-
136
+
161
137
  if critical:
162
138
  warnings.append(
163
139
  f"🔒 CRITICAL: {len(critical)} security-critical checks failed - publishing BLOCKED"
164
140
  )
165
141
  for failure in critical:
166
- reason = self.CRITICAL_HOOKS.get(failure.hook_name.lower(), "Security-critical check")
167
- warnings.append(f" â€ĸ {failure.hook_name}: {reason}")
168
-
142
+ reason = self.CRITICAL_HOOKS.get(
143
+ failure.hook_name.lower(), "Security-critical check"
144
+ )
145
+ warnings.append(f" â€ĸ {failure.hook_name}: {reason}")
146
+
169
147
  if high:
170
148
  warnings.append(
171
149
  f"âš ī¸ HIGH: {len(high)} high-security checks failed - review recommended"
172
150
  )
173
-
151
+
174
152
  if medium:
175
- warnings.append(
176
- f"â„šī¸ MEDIUM: {len(medium)} standard quality checks failed"
177
- )
178
-
153
+ warnings.append(f"â„šī¸ MEDIUM: {len(medium)} standard quality checks failed")
154
+
179
155
  return warnings
180
-
156
+
181
157
  def _generate_security_recommendations(
182
158
  self,
183
159
  critical: list[SecurityCheckResult],
184
160
  high: list[SecurityCheckResult],
185
- medium: list[SecurityCheckResult]
161
+ medium: list[SecurityCheckResult],
186
162
  ) -> list[str]:
187
- """Generate security recommendations based on OWASP best practices."""
188
163
  recommendations = []
189
-
164
+
190
165
  if critical:
191
- recommendations.append("🔧 Fix all CRITICAL security issues before publishing")
192
-
193
- # Specific recommendations based on failed checks
166
+ recommendations.append(
167
+ "🔧 Fix all CRITICAL security issues before publishing"
168
+ )
169
+
194
170
  critical_names = [f.hook_name.lower() for f in critical]
195
-
196
- if 'bandit' in critical_names:
197
- recommendations.append(" â€ĸ Review bandit security findings - may indicate vulnerabilities")
198
- if 'pyright' in critical_names:
199
- recommendations.append(" â€ĸ Fix type errors - type safety prevents runtime security holes")
200
- if 'gitleaks' in critical_names:
201
- recommendations.append(" â€ĸ Remove secrets/credentials from code - use environment variables")
202
-
171
+
172
+ if "bandit" in critical_names:
173
+ recommendations.append(
174
+ " â€ĸ Review bandit security findings - may indicate vulnerabilities"
175
+ )
176
+ if "pyright" in critical_names:
177
+ recommendations.append(
178
+ " â€ĸ Fix type errors - type safety prevents runtime security holes"
179
+ )
180
+ if "gitleaks" in critical_names:
181
+ recommendations.append(
182
+ " â€ĸ Remove secrets/credentials from code - use environment variables"
183
+ )
184
+
203
185
  if high:
204
- recommendations.append("🔍 Review HIGH-security findings before production deployment")
205
-
206
- if len(critical) == 0 and len(high) == 0:
186
+ recommendations.append(
187
+ "🔍 Review HIGH-security findings before production deployment"
188
+ )
189
+
190
+ if not critical and not high:
207
191
  recommendations.append("✅ Security posture is acceptable for publishing")
208
-
209
- # Add OWASP best practices reference
210
- recommendations.append("📖 Follow OWASP Secure Coding Practices for comprehensive security")
211
-
212
- return recommendations
192
+
193
+ recommendations.append(
194
+ "📖 Follow OWASP Secure Coding Practices for comprehensive security"
195
+ )
196
+
197
+ return recommendations