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.
- 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 +64 -6
- 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 +257 -218
- 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 +558 -240
- 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 +66 -13
- crackerjack/managers/test_command_builder.py +5 -17
- crackerjack/managers/test_executor.py +1 -3
- crackerjack/managers/test_manager.py +109 -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 +161 -32
- 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 +174 -33
- crackerjack/mcp/tools/error_analyzer.py +3 -2
- crackerjack/mcp/tools/execution_tools.py +15 -12
- 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 +3 -0
- crackerjack/mixins/error_handling.py +145 -0
- crackerjack/models/config.py +21 -1
- crackerjack/models/config_adapter.py +49 -1
- crackerjack/models/protocols.py +176 -107
- crackerjack/models/resource_protocols.py +55 -210
- crackerjack/models/task.py +3 -0
- 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 +90 -105
- 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 +18 -11
- crackerjack/services/config_merge.py +30 -85
- 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 +41 -17
- 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 +41 -45
- crackerjack/services/health_metrics.py +10 -8
- crackerjack/services/heatmap_generator.py +735 -0
- crackerjack/services/initialization.py +30 -33
- 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 +409 -0
- crackerjack/services/metrics.py +42 -33
- crackerjack/services/parallel_executor.py +416 -0
- crackerjack/services/pattern_cache.py +1 -1
- crackerjack/services/pattern_detector.py +6 -6
- crackerjack/services/performance_benchmarks.py +250 -576
- crackerjack/services/performance_cache.py +382 -0
- crackerjack/services/performance_monitor.py +565 -0
- 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 +605 -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 +61 -30
- crackerjack/services/security_logger.py +18 -22
- crackerjack/services/server_manager.py +124 -16
- crackerjack/services/status_authentication.py +16 -159
- crackerjack/services/status_security_manager.py +4 -131
- crackerjack/services/terminal_utils.py +0 -0
- 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.32.0.dist-info â crackerjack-0.33.1.dist-info}/METADATA +197 -26
- crackerjack-0.33.1.dist-info/RECORD +229 -0
- crackerjack/CLAUDE.md +0 -207
- crackerjack/RULES.md +0 -380
- crackerjack/py313.py +0 -234
- crackerjack-0.32.0.dist-info/RECORD +0 -180
- {crackerjack-0.32.0.dist-info â crackerjack-0.33.1.dist-info}/WHEEL +0 -0
- {crackerjack-0.32.0.dist-info â crackerjack-0.33.1.dist-info}/entry_points.txt +0 -0
- {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.
|
|
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,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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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={
|
|
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
|
|
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
|
|
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
|
|
125
|
+
elif hook_name_lower in ("ruff-check", "vulture", "refurb", "complexipy"):
|
|
148
126
|
return SecurityLevel.MEDIUM
|
|
149
|
-
|
|
150
|
-
|
|
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(
|
|
167
|
-
|
|
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
|
-
|
|
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(
|
|
192
|
-
|
|
193
|
-
|
|
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
|
|
197
|
-
recommendations.append(
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
if
|
|
201
|
-
recommendations.append(
|
|
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(
|
|
205
|
-
|
|
206
|
-
|
|
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
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
192
|
+
|
|
193
|
+
recommendations.append(
|
|
194
|
+
"đ Follow OWASP Secure Coding Practices for comprehensive security"
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
return recommendations
|