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.
- crackerjack/__main__.py +1350 -34
- crackerjack/adapters/__init__.py +17 -0
- crackerjack/adapters/lsp_client.py +358 -0
- crackerjack/adapters/rust_tool_adapter.py +194 -0
- crackerjack/adapters/rust_tool_manager.py +193 -0
- crackerjack/adapters/skylos_adapter.py +231 -0
- crackerjack/adapters/zuban_adapter.py +560 -0
- crackerjack/agents/base.py +7 -3
- crackerjack/agents/coordinator.py +271 -33
- crackerjack/agents/documentation_agent.py +9 -15
- crackerjack/agents/dry_agent.py +3 -15
- crackerjack/agents/formatting_agent.py +1 -1
- crackerjack/agents/import_optimization_agent.py +36 -180
- crackerjack/agents/performance_agent.py +17 -98
- crackerjack/agents/performance_helpers.py +7 -31
- crackerjack/agents/proactive_agent.py +1 -3
- crackerjack/agents/refactoring_agent.py +16 -85
- crackerjack/agents/refactoring_helpers.py +7 -42
- crackerjack/agents/security_agent.py +9 -48
- crackerjack/agents/test_creation_agent.py +356 -513
- crackerjack/agents/test_specialist_agent.py +0 -4
- crackerjack/api.py +6 -25
- crackerjack/cli/cache_handlers.py +204 -0
- crackerjack/cli/cache_handlers_enhanced.py +683 -0
- crackerjack/cli/facade.py +100 -0
- crackerjack/cli/handlers.py +224 -9
- crackerjack/cli/interactive.py +6 -4
- crackerjack/cli/options.py +642 -55
- crackerjack/cli/utils.py +2 -1
- crackerjack/code_cleaner.py +58 -117
- crackerjack/config/global_lock_config.py +8 -48
- crackerjack/config/hooks.py +53 -62
- crackerjack/core/async_workflow_orchestrator.py +24 -34
- crackerjack/core/autofix_coordinator.py +3 -17
- crackerjack/core/enhanced_container.py +4 -13
- crackerjack/core/file_lifecycle.py +12 -89
- crackerjack/core/performance.py +2 -2
- crackerjack/core/performance_monitor.py +15 -55
- crackerjack/core/phase_coordinator.py +104 -204
- crackerjack/core/resource_manager.py +14 -90
- crackerjack/core/service_watchdog.py +62 -95
- crackerjack/core/session_coordinator.py +149 -0
- crackerjack/core/timeout_manager.py +14 -72
- crackerjack/core/websocket_lifecycle.py +13 -78
- crackerjack/core/workflow_orchestrator.py +171 -174
- crackerjack/docs/INDEX.md +11 -0
- crackerjack/docs/generated/api/API_REFERENCE.md +10895 -0
- crackerjack/docs/generated/api/CLI_REFERENCE.md +109 -0
- crackerjack/docs/generated/api/CROSS_REFERENCES.md +1755 -0
- crackerjack/docs/generated/api/PROTOCOLS.md +3 -0
- crackerjack/docs/generated/api/SERVICES.md +1252 -0
- crackerjack/documentation/__init__.py +31 -0
- crackerjack/documentation/ai_templates.py +756 -0
- crackerjack/documentation/dual_output_generator.py +765 -0
- crackerjack/documentation/mkdocs_integration.py +518 -0
- crackerjack/documentation/reference_generator.py +977 -0
- crackerjack/dynamic_config.py +55 -50
- crackerjack/executors/async_hook_executor.py +10 -15
- crackerjack/executors/cached_hook_executor.py +117 -43
- crackerjack/executors/hook_executor.py +8 -34
- crackerjack/executors/hook_lock_manager.py +26 -183
- crackerjack/executors/individual_hook_executor.py +13 -11
- crackerjack/executors/lsp_aware_hook_executor.py +270 -0
- crackerjack/executors/tool_proxy.py +417 -0
- crackerjack/hooks/lsp_hook.py +79 -0
- crackerjack/intelligence/adaptive_learning.py +25 -10
- crackerjack/intelligence/agent_orchestrator.py +2 -5
- crackerjack/intelligence/agent_registry.py +34 -24
- crackerjack/intelligence/agent_selector.py +5 -7
- crackerjack/interactive.py +17 -6
- crackerjack/managers/async_hook_manager.py +0 -1
- crackerjack/managers/hook_manager.py +79 -1
- crackerjack/managers/publish_manager.py +44 -8
- crackerjack/managers/test_command_builder.py +1 -15
- crackerjack/managers/test_executor.py +1 -3
- crackerjack/managers/test_manager.py +98 -7
- crackerjack/managers/test_manager_backup.py +10 -9
- crackerjack/mcp/cache.py +2 -2
- crackerjack/mcp/client_runner.py +1 -1
- crackerjack/mcp/context.py +191 -68
- crackerjack/mcp/dashboard.py +7 -5
- crackerjack/mcp/enhanced_progress_monitor.py +31 -28
- crackerjack/mcp/file_monitor.py +30 -23
- crackerjack/mcp/progress_components.py +31 -21
- crackerjack/mcp/progress_monitor.py +50 -53
- crackerjack/mcp/rate_limiter.py +6 -6
- crackerjack/mcp/server_core.py +17 -16
- crackerjack/mcp/service_watchdog.py +2 -1
- crackerjack/mcp/state.py +4 -7
- crackerjack/mcp/task_manager.py +11 -9
- crackerjack/mcp/tools/core_tools.py +173 -32
- crackerjack/mcp/tools/error_analyzer.py +3 -2
- crackerjack/mcp/tools/execution_tools.py +8 -10
- crackerjack/mcp/tools/execution_tools_backup.py +42 -30
- crackerjack/mcp/tools/intelligence_tool_registry.py +7 -5
- crackerjack/mcp/tools/intelligence_tools.py +5 -2
- crackerjack/mcp/tools/monitoring_tools.py +33 -70
- crackerjack/mcp/tools/proactive_tools.py +24 -11
- crackerjack/mcp/tools/progress_tools.py +5 -8
- crackerjack/mcp/tools/utility_tools.py +20 -14
- crackerjack/mcp/tools/workflow_executor.py +62 -40
- crackerjack/mcp/websocket/app.py +8 -0
- crackerjack/mcp/websocket/endpoints.py +352 -357
- crackerjack/mcp/websocket/jobs.py +40 -57
- crackerjack/mcp/websocket/monitoring_endpoints.py +2935 -0
- crackerjack/mcp/websocket/server.py +7 -25
- crackerjack/mcp/websocket/websocket_handler.py +6 -17
- crackerjack/mixins/__init__.py +0 -2
- crackerjack/mixins/error_handling.py +1 -70
- crackerjack/models/config.py +12 -1
- crackerjack/models/config_adapter.py +49 -1
- crackerjack/models/protocols.py +122 -122
- crackerjack/models/resource_protocols.py +55 -210
- crackerjack/monitoring/ai_agent_watchdog.py +13 -13
- crackerjack/monitoring/metrics_collector.py +426 -0
- crackerjack/monitoring/regression_prevention.py +8 -8
- crackerjack/monitoring/websocket_server.py +643 -0
- crackerjack/orchestration/advanced_orchestrator.py +11 -6
- crackerjack/orchestration/coverage_improvement.py +3 -3
- crackerjack/orchestration/execution_strategies.py +26 -6
- crackerjack/orchestration/test_progress_streamer.py +8 -5
- crackerjack/plugins/base.py +2 -2
- crackerjack/plugins/hooks.py +7 -0
- crackerjack/plugins/managers.py +11 -8
- crackerjack/security/__init__.py +0 -1
- crackerjack/security/audit.py +6 -35
- crackerjack/services/anomaly_detector.py +392 -0
- crackerjack/services/api_extractor.py +615 -0
- crackerjack/services/backup_service.py +2 -2
- crackerjack/services/bounded_status_operations.py +15 -152
- crackerjack/services/cache.py +127 -1
- crackerjack/services/changelog_automation.py +395 -0
- crackerjack/services/config.py +15 -9
- crackerjack/services/config_merge.py +19 -80
- crackerjack/services/config_template.py +506 -0
- crackerjack/services/contextual_ai_assistant.py +48 -22
- crackerjack/services/coverage_badge_service.py +171 -0
- crackerjack/services/coverage_ratchet.py +27 -25
- crackerjack/services/debug.py +3 -3
- crackerjack/services/dependency_analyzer.py +460 -0
- crackerjack/services/dependency_monitor.py +14 -11
- crackerjack/services/documentation_generator.py +491 -0
- crackerjack/services/documentation_service.py +675 -0
- crackerjack/services/enhanced_filesystem.py +6 -5
- crackerjack/services/enterprise_optimizer.py +865 -0
- crackerjack/services/error_pattern_analyzer.py +676 -0
- crackerjack/services/file_hasher.py +1 -1
- crackerjack/services/git.py +8 -25
- crackerjack/services/health_metrics.py +10 -8
- crackerjack/services/heatmap_generator.py +735 -0
- crackerjack/services/initialization.py +11 -30
- crackerjack/services/input_validator.py +5 -97
- crackerjack/services/intelligent_commit.py +327 -0
- crackerjack/services/log_manager.py +15 -12
- crackerjack/services/logging.py +4 -3
- crackerjack/services/lsp_client.py +628 -0
- crackerjack/services/memory_optimizer.py +19 -87
- crackerjack/services/metrics.py +42 -33
- crackerjack/services/parallel_executor.py +9 -67
- crackerjack/services/pattern_cache.py +1 -1
- crackerjack/services/pattern_detector.py +6 -6
- crackerjack/services/performance_benchmarks.py +18 -59
- crackerjack/services/performance_cache.py +20 -81
- crackerjack/services/performance_monitor.py +27 -95
- crackerjack/services/predictive_analytics.py +510 -0
- crackerjack/services/quality_baseline.py +234 -0
- crackerjack/services/quality_baseline_enhanced.py +646 -0
- crackerjack/services/quality_intelligence.py +785 -0
- crackerjack/services/regex_patterns.py +618 -524
- crackerjack/services/regex_utils.py +43 -123
- crackerjack/services/secure_path_utils.py +5 -164
- crackerjack/services/secure_status_formatter.py +30 -141
- crackerjack/services/secure_subprocess.py +11 -92
- crackerjack/services/security.py +9 -41
- crackerjack/services/security_logger.py +12 -24
- crackerjack/services/server_manager.py +124 -16
- crackerjack/services/status_authentication.py +16 -159
- crackerjack/services/status_security_manager.py +4 -131
- crackerjack/services/thread_safe_status_collector.py +19 -125
- crackerjack/services/unified_config.py +21 -13
- crackerjack/services/validation_rate_limiter.py +5 -54
- crackerjack/services/version_analyzer.py +459 -0
- crackerjack/services/version_checker.py +1 -1
- crackerjack/services/websocket_resource_limiter.py +10 -144
- crackerjack/services/zuban_lsp_service.py +390 -0
- crackerjack/slash_commands/__init__.py +2 -7
- crackerjack/slash_commands/run.md +2 -2
- crackerjack/tools/validate_input_validator_patterns.py +14 -40
- crackerjack/tools/validate_regex_patterns.py +19 -48
- {crackerjack-0.33.0.dist-info → crackerjack-0.33.2.dist-info}/METADATA +196 -25
- crackerjack-0.33.2.dist-info/RECORD +229 -0
- crackerjack/CLAUDE.md +0 -207
- crackerjack/RULES.md +0 -380
- crackerjack/py313.py +0 -234
- crackerjack-0.33.0.dist-info/RECORD +0 -187
- {crackerjack-0.33.0.dist-info → crackerjack-0.33.2.dist-info}/WHEEL +0 -0
- {crackerjack-0.33.0.dist-info → crackerjack-0.33.2.dist-info}/entry_points.txt +0 -0
- {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.
|
|
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.
|
|
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.
|
|
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:
|
crackerjack/plugins/base.py
CHANGED
|
@@ -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
|
|
crackerjack/plugins/hooks.py
CHANGED
|
@@ -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)
|
crackerjack/plugins/managers.py
CHANGED
|
@@ -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
|
|
63
|
-
plugin
|
|
64
|
-
|
|
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,
|
crackerjack/security/__init__.py
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"""Security utilities for Crackerjack."""
|
crackerjack/security/audit.py
CHANGED
|
@@ -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
|
|
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
|
|
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"
|
|
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
|
-
"
|
|
174
|
+
" • Review bandit security findings - may indicate vulnerabilities"
|
|
203
175
|
)
|
|
204
176
|
if "pyright" in critical_names:
|
|
205
177
|
recommendations.append(
|
|
206
|
-
"
|
|
178
|
+
" • Fix type errors - type safety prevents runtime security holes"
|
|
207
179
|
)
|
|
208
180
|
if "gitleaks" in critical_names:
|
|
209
181
|
recommendations.append(
|
|
210
|
-
"
|
|
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
|
)
|