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
@@ -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,5 +1,3 @@
1
- """Security audit utilities for secure SDLC practices."""
2
-
3
1
  import typing as t
4
2
  from dataclasses import dataclass
5
3
 
@@ -8,8 +6,6 @@ from crackerjack.config.hooks import SecurityLevel
8
6
 
9
7
  @dataclass
10
8
  class SecurityCheckResult:
11
- """Result of a security check."""
12
-
13
9
  hook_name: str
14
10
  security_level: SecurityLevel
15
11
  passed: bool
@@ -19,8 +15,6 @@ class SecurityCheckResult:
19
15
 
20
16
  @dataclass
21
17
  class SecurityAuditReport:
22
- """Comprehensive security audit report for publishing decisions."""
23
-
24
18
  critical_failures: list[SecurityCheckResult]
25
19
  high_failures: list[SecurityCheckResult]
26
20
  medium_failures: list[SecurityCheckResult]
@@ -32,12 +26,10 @@ class SecurityAuditReport:
32
26
 
33
27
  @property
34
28
  def has_critical_failures(self) -> bool:
35
- """Check if there are any critical security failures."""
36
29
  return len(self.critical_failures) > 0
37
30
 
38
31
  @property
39
32
  def total_failures(self) -> int:
40
- """Get total number of failed checks."""
41
33
  return (
42
34
  len(self.critical_failures)
43
35
  + len(self.high_failures)
@@ -47,16 +39,12 @@ class SecurityAuditReport:
47
39
 
48
40
 
49
41
  class SecurityAuditor:
50
- """Security auditor for hook results following OWASP secure SDLC practices."""
51
-
52
- # Security-critical hooks that CANNOT be bypassed for publishing
53
42
  CRITICAL_HOOKS = {
54
43
  "bandit": "Security vulnerability detection (OWASP A09)",
55
44
  "pyright": "Type safety prevents runtime security holes (OWASP A04)",
56
45
  "gitleaks": "Secret/credential detection (OWASP A07)",
57
46
  }
58
47
 
59
- # High-importance security hooks that can be bypassed with warnings
60
48
  HIGH_SECURITY_HOOKS = {
61
49
  "validate-regex-patterns": "Regex vulnerability detection",
62
50
  "creosote": "Dependency vulnerability analysis",
@@ -67,15 +55,6 @@ class SecurityAuditor:
67
55
  def audit_hook_results(
68
56
  self, fast_results: list[t.Any], comprehensive_results: list[t.Any]
69
57
  ) -> SecurityAuditReport:
70
- """Audit hook results and generate security report.
71
-
72
- Args:
73
- fast_results: Results from fast hooks
74
- comprehensive_results: Results from comprehensive hooks
75
-
76
- Returns:
77
- SecurityAuditReport with security analysis
78
- """
79
58
  all_results = fast_results + comprehensive_results
80
59
 
81
60
  critical_failures = []
@@ -95,7 +74,6 @@ class SecurityAuditor:
95
74
  else:
96
75
  low_failures.append(check_result)
97
76
 
98
- # Publishing is allowed only if no critical failures exist
99
77
  allows_publishing = len(critical_failures) == 0
100
78
 
101
79
  security_warnings = self._generate_security_warnings(
@@ -117,7 +95,6 @@ class SecurityAuditor:
117
95
  )
118
96
 
119
97
  def _analyze_hook_result(self, result: t.Any) -> SecurityCheckResult:
120
- """Analyze a single hook result for security implications."""
121
98
  hook_name = getattr(result, "name", "unknown")
122
99
  is_failed = getattr(result, "status", "unknown") in (
123
100
  "failed",
@@ -128,7 +105,6 @@ class SecurityAuditor:
128
105
  result, "error", None
129
106
  )
130
107
 
131
- # Determine security level
132
108
  security_level = self._get_hook_security_level(hook_name)
133
109
 
134
110
  return SecurityCheckResult(
@@ -140,12 +116,11 @@ class SecurityAuditor:
140
116
  )
141
117
 
142
118
  def _get_hook_security_level(self, hook_name: str) -> SecurityLevel:
143
- """Get security level for a hook name."""
144
119
  hook_name_lower = hook_name.lower()
145
120
 
146
- if hook_name_lower in [name.lower() for name in self.CRITICAL_HOOKS]:
121
+ if hook_name_lower in (name.lower() for name in self.CRITICAL_HOOKS):
147
122
  return SecurityLevel.CRITICAL
148
- 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):
149
124
  return SecurityLevel.HIGH
150
125
  elif hook_name_lower in ("ruff-check", "vulture", "refurb", "complexipy"):
151
126
  return SecurityLevel.MEDIUM
@@ -157,7 +132,6 @@ class SecurityAuditor:
157
132
  high: list[SecurityCheckResult],
158
133
  medium: list[SecurityCheckResult],
159
134
  ) -> list[str]:
160
- """Generate security warnings based on failed checks."""
161
135
  warnings = []
162
136
 
163
137
  if critical:
@@ -168,7 +142,7 @@ class SecurityAuditor:
168
142
  reason = self.CRITICAL_HOOKS.get(
169
143
  failure.hook_name.lower(), "Security-critical check"
170
144
  )
171
- warnings.append(f" • {failure.hook_name}: {reason}")
145
+ warnings.append(f" • {failure.hook_name}: {reason}")
172
146
 
173
147
  if high:
174
148
  warnings.append(
@@ -186,7 +160,6 @@ class SecurityAuditor:
186
160
  high: list[SecurityCheckResult],
187
161
  medium: list[SecurityCheckResult],
188
162
  ) -> list[str]:
189
- """Generate security recommendations based on OWASP best practices."""
190
163
  recommendations = []
191
164
 
192
165
  if critical:
@@ -194,20 +167,19 @@ class SecurityAuditor:
194
167
  "🔧 Fix all CRITICAL security issues before publishing"
195
168
  )
196
169
 
197
- # Specific recommendations based on failed checks
198
170
  critical_names = [f.hook_name.lower() for f in critical]
199
171
 
200
172
  if "bandit" in critical_names:
201
173
  recommendations.append(
202
- " • Review bandit security findings - may indicate vulnerabilities"
174
+ " • Review bandit security findings - may indicate vulnerabilities"
203
175
  )
204
176
  if "pyright" in critical_names:
205
177
  recommendations.append(
206
- " • Fix type errors - type safety prevents runtime security holes"
178
+ " • Fix type errors - type safety prevents runtime security holes"
207
179
  )
208
180
  if "gitleaks" in critical_names:
209
181
  recommendations.append(
210
- " • Remove secrets/credentials from code - use environment variables"
182
+ " • Remove secrets/credentials from code - use environment variables"
211
183
  )
212
184
 
213
185
  if high:
@@ -218,7 +190,6 @@ class SecurityAuditor:
218
190
  if not critical and not high:
219
191
  recommendations.append("✅ Security posture is acceptable for publishing")
220
192
 
221
- # Add OWASP best practices reference
222
193
  recommendations.append(
223
194
  "📖 Follow OWASP Secure Coding Practices for comprehensive security"
224
195
  )