crackerjack 0.33.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 +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 +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 +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.1.dist-info}/METADATA +196 -25
- 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.33.0.dist-info/RECORD +0 -187
- {crackerjack-0.33.0.dist-info → crackerjack-0.33.1.dist-info}/WHEEL +0 -0
- {crackerjack-0.33.0.dist-info → crackerjack-0.33.1.dist-info}/entry_points.txt +0 -0
- {crackerjack-0.33.0.dist-info → crackerjack-0.33.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
"""Zuban Language Server Protocol (LSP) service for real-time type checking."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import json
|
|
5
|
+
import logging
|
|
6
|
+
import subprocess
|
|
7
|
+
import time
|
|
8
|
+
import typing as t
|
|
9
|
+
from contextlib import suppress
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from rich.console import Console
|
|
13
|
+
|
|
14
|
+
from .security_logger import get_security_logger
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger("crackerjack.zuban_lsp")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ZubanLSPService:
|
|
20
|
+
"""Manages zuban language server lifecycle and communication."""
|
|
21
|
+
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
port: int = 8677,
|
|
25
|
+
mode: str = "tcp",
|
|
26
|
+
console: Console | None = None,
|
|
27
|
+
) -> None:
|
|
28
|
+
"""Initialize Zuban LSP service.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
port: TCP port for server (default: 8677)
|
|
32
|
+
mode: Transport mode, "tcp" or "stdio" (default: "tcp")
|
|
33
|
+
console: Rich console for output (optional)
|
|
34
|
+
"""
|
|
35
|
+
self.port = port
|
|
36
|
+
self.mode = mode
|
|
37
|
+
self.console = console or Console()
|
|
38
|
+
self.process: subprocess.Popen[bytes] | None = None
|
|
39
|
+
self.start_time: float = 0.0
|
|
40
|
+
self.security_logger = get_security_logger()
|
|
41
|
+
self._health_check_failures = 0
|
|
42
|
+
self._max_health_failures = 3
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def is_running(self) -> bool:
|
|
46
|
+
"""Check if LSP server process is running."""
|
|
47
|
+
return self.process is not None and self.process.poll() is None
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def uptime(self) -> float:
|
|
51
|
+
"""Get server uptime in seconds."""
|
|
52
|
+
if self.is_running and self.start_time > 0:
|
|
53
|
+
return time.time() - self.start_time
|
|
54
|
+
return 0.0
|
|
55
|
+
|
|
56
|
+
async def start(self) -> bool:
|
|
57
|
+
"""Start the zuban LSP server.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
True if started successfully, False otherwise
|
|
61
|
+
"""
|
|
62
|
+
if self.is_running:
|
|
63
|
+
logger.info("Zuban LSP server already running")
|
|
64
|
+
return True
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
self.console.print("[cyan]🚀 Starting Zuban LSP server...[/cyan]")
|
|
68
|
+
|
|
69
|
+
# Build command based on transport mode
|
|
70
|
+
if self.mode == "tcp":
|
|
71
|
+
# For TCP mode, we'll need to configure zuban to listen on port
|
|
72
|
+
# Currently zuban server only supports stdio, so we use stdio mode
|
|
73
|
+
cmd = ["uv", "run", "zuban", "server"]
|
|
74
|
+
else:
|
|
75
|
+
cmd = ["uv", "run", "zuban", "server"]
|
|
76
|
+
|
|
77
|
+
# Start the process
|
|
78
|
+
self.process = subprocess.Popen(
|
|
79
|
+
cmd,
|
|
80
|
+
stdin=subprocess.PIPE,
|
|
81
|
+
stdout=subprocess.PIPE,
|
|
82
|
+
stderr=subprocess.PIPE,
|
|
83
|
+
cwd=Path.cwd(),
|
|
84
|
+
start_new_session=True,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
self.start_time = time.time()
|
|
88
|
+
self._health_check_failures = 0
|
|
89
|
+
|
|
90
|
+
# Log the startup
|
|
91
|
+
self.security_logger.log_subprocess_execution(
|
|
92
|
+
command=cmd,
|
|
93
|
+
purpose="zuban_lsp_server_start",
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
# Wait a moment for startup
|
|
97
|
+
await asyncio.sleep(1.0)
|
|
98
|
+
|
|
99
|
+
# Verify it started successfully
|
|
100
|
+
if not self.is_running:
|
|
101
|
+
error_output = ""
|
|
102
|
+
if self.process and self.process.stderr:
|
|
103
|
+
with suppress(Exception):
|
|
104
|
+
error_output = self.process.stderr.read().decode()
|
|
105
|
+
|
|
106
|
+
self.console.print("[red]❌ Failed to start Zuban LSP server[/red]")
|
|
107
|
+
if error_output:
|
|
108
|
+
logger.error(f"Zuban LSP startup error: {error_output}")
|
|
109
|
+
return False
|
|
110
|
+
|
|
111
|
+
self.console.print(
|
|
112
|
+
f"[green]✅ Zuban LSP server started (PID: {self.process.pid})[/green]"
|
|
113
|
+
)
|
|
114
|
+
logger.info(f"Zuban LSP server started with PID {self.process.pid}")
|
|
115
|
+
return True
|
|
116
|
+
|
|
117
|
+
except Exception as e:
|
|
118
|
+
self.console.print(f"[red]❌ Error starting Zuban LSP server: {e}[/red]")
|
|
119
|
+
logger.error(f"Failed to start Zuban LSP server: {e}")
|
|
120
|
+
return False
|
|
121
|
+
|
|
122
|
+
async def stop(self) -> None:
|
|
123
|
+
"""Gracefully stop the LSP server."""
|
|
124
|
+
if not self.process:
|
|
125
|
+
return
|
|
126
|
+
|
|
127
|
+
self.console.print("[yellow]🛑 Stopping Zuban LSP server...[/yellow]")
|
|
128
|
+
|
|
129
|
+
try:
|
|
130
|
+
# Try graceful shutdown first
|
|
131
|
+
self.process.terminate()
|
|
132
|
+
|
|
133
|
+
# Wait for graceful shutdown
|
|
134
|
+
try:
|
|
135
|
+
self.process.wait(timeout=5.0)
|
|
136
|
+
self.console.print(
|
|
137
|
+
"[green]✅ Zuban LSP server stopped gracefully[/green]"
|
|
138
|
+
)
|
|
139
|
+
except subprocess.TimeoutExpired:
|
|
140
|
+
# Force kill if graceful shutdown fails
|
|
141
|
+
self.process.kill()
|
|
142
|
+
self.process.wait(timeout=2.0)
|
|
143
|
+
self.console.print("[yellow]⚠️ Zuban LSP server force stopped[/yellow]")
|
|
144
|
+
|
|
145
|
+
except Exception as e:
|
|
146
|
+
logger.error(f"Error stopping Zuban LSP server: {e}")
|
|
147
|
+
|
|
148
|
+
finally:
|
|
149
|
+
self.process = None
|
|
150
|
+
self.start_time = 0.0
|
|
151
|
+
self._health_check_failures = 0
|
|
152
|
+
|
|
153
|
+
async def health_check(self) -> bool:
|
|
154
|
+
"""Check if LSP server is responsive.
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
True if server is healthy, False otherwise
|
|
158
|
+
"""
|
|
159
|
+
if not self.is_running:
|
|
160
|
+
return False
|
|
161
|
+
|
|
162
|
+
try:
|
|
163
|
+
# For stdio mode, we check if process is alive and responsive
|
|
164
|
+
if self.mode == "stdio":
|
|
165
|
+
return self._check_stdio_health()
|
|
166
|
+
else:
|
|
167
|
+
# For TCP mode, we would check port connectivity
|
|
168
|
+
return self._check_tcp_health()
|
|
169
|
+
|
|
170
|
+
except Exception as e:
|
|
171
|
+
logger.warning(f"Health check failed: {e}")
|
|
172
|
+
self._health_check_failures += 1
|
|
173
|
+
return False
|
|
174
|
+
|
|
175
|
+
def _check_stdio_health(self) -> bool:
|
|
176
|
+
"""Check health for stdio mode server."""
|
|
177
|
+
if not self.process:
|
|
178
|
+
return False
|
|
179
|
+
|
|
180
|
+
# Simple check - is process still alive?
|
|
181
|
+
return self.process.poll() is None
|
|
182
|
+
|
|
183
|
+
def _check_tcp_health(self) -> bool:
|
|
184
|
+
"""Check health for TCP mode server."""
|
|
185
|
+
# TODO: Implement TCP health check when zuban supports TCP mode
|
|
186
|
+
# For now, fall back to process check
|
|
187
|
+
return self._check_stdio_health()
|
|
188
|
+
|
|
189
|
+
async def restart(self) -> bool:
|
|
190
|
+
"""Restart the LSP server.
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
True if restarted successfully, False otherwise
|
|
194
|
+
"""
|
|
195
|
+
self.console.print("[cyan]🔄 Restarting Zuban LSP server...[/cyan]")
|
|
196
|
+
|
|
197
|
+
await self.stop()
|
|
198
|
+
await asyncio.sleep(2.0) # Brief pause between stop and start
|
|
199
|
+
|
|
200
|
+
success = await self.start()
|
|
201
|
+
if success:
|
|
202
|
+
self.console.print(
|
|
203
|
+
"[green]✅ Zuban LSP server restarted successfully[/green]"
|
|
204
|
+
)
|
|
205
|
+
else:
|
|
206
|
+
self.console.print("[red]❌ Failed to restart Zuban LSP server[/red]")
|
|
207
|
+
|
|
208
|
+
return success
|
|
209
|
+
|
|
210
|
+
def get_status(self) -> dict[str, t.Any]:
|
|
211
|
+
"""Get current status of the LSP server.
|
|
212
|
+
|
|
213
|
+
Returns:
|
|
214
|
+
Dictionary with server status information
|
|
215
|
+
"""
|
|
216
|
+
return {
|
|
217
|
+
"running": self.is_running,
|
|
218
|
+
"pid": self.process.pid if self.process else None,
|
|
219
|
+
"uptime": self.uptime,
|
|
220
|
+
"port": self.port,
|
|
221
|
+
"mode": self.mode,
|
|
222
|
+
"health_failures": self._health_check_failures,
|
|
223
|
+
"max_health_failures": self._max_health_failures,
|
|
224
|
+
"healthy": self.is_running
|
|
225
|
+
and self._health_check_failures < self._max_health_failures,
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async def send_lsp_request(
|
|
229
|
+
self, method: str, params: dict[str, t.Any] | None = None
|
|
230
|
+
) -> dict[str, t.Any] | None:
|
|
231
|
+
"""Send an LSP request to the server.
|
|
232
|
+
|
|
233
|
+
Args:
|
|
234
|
+
method: LSP method name (e.g., "initialize", "textDocument/didOpen")
|
|
235
|
+
params: Request parameters (optional)
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
LSP response or None if failed
|
|
239
|
+
"""
|
|
240
|
+
if not self.is_running or not self.process or not self.process.stdin:
|
|
241
|
+
return None
|
|
242
|
+
|
|
243
|
+
try:
|
|
244
|
+
request_id = int(time.time() * 1000)
|
|
245
|
+
request = {
|
|
246
|
+
"jsonrpc": "2.0",
|
|
247
|
+
"id": request_id,
|
|
248
|
+
"method": method,
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if params is not None:
|
|
252
|
+
request["params"] = params
|
|
253
|
+
|
|
254
|
+
request_json = json.dumps(request)
|
|
255
|
+
content_length = len(request_json.encode())
|
|
256
|
+
|
|
257
|
+
# LSP protocol: Content-Length header + \r\n\r\n + JSON
|
|
258
|
+
message = f"Content-Length: {content_length}\r\n\r\n{request_json}"
|
|
259
|
+
|
|
260
|
+
self.process.stdin.write(message.encode())
|
|
261
|
+
self.process.stdin.flush()
|
|
262
|
+
|
|
263
|
+
# For notifications (no response expected), return success
|
|
264
|
+
if method.startswith("textDocument/did"):
|
|
265
|
+
return {"status": "notification_sent", "id": request_id}
|
|
266
|
+
|
|
267
|
+
# For requests that expect responses, attempt to read response
|
|
268
|
+
try:
|
|
269
|
+
response = await self._read_lsp_response(request_id, timeout=5.0)
|
|
270
|
+
return response
|
|
271
|
+
except TimeoutError:
|
|
272
|
+
logger.warning(f"LSP request {method} timed out")
|
|
273
|
+
return {"status": "timeout", "id": request_id}
|
|
274
|
+
|
|
275
|
+
except Exception as e:
|
|
276
|
+
logger.error(f"Failed to send LSP request: {e}")
|
|
277
|
+
return None
|
|
278
|
+
|
|
279
|
+
async def _read_lsp_response(
|
|
280
|
+
self, expected_id: int, timeout: float = 5.0
|
|
281
|
+
) -> dict[str, t.Any] | None:
|
|
282
|
+
"""Read LSP response from server with timeout.
|
|
283
|
+
|
|
284
|
+
Args:
|
|
285
|
+
expected_id: Expected request ID for the response
|
|
286
|
+
timeout: Timeout in seconds
|
|
287
|
+
|
|
288
|
+
Returns:
|
|
289
|
+
LSP response dictionary or None if failed
|
|
290
|
+
"""
|
|
291
|
+
if not self.process or not self.process.stdout:
|
|
292
|
+
return None
|
|
293
|
+
|
|
294
|
+
try:
|
|
295
|
+
# Read with timeout
|
|
296
|
+
response_data = await asyncio.wait_for(
|
|
297
|
+
self._read_message_from_stdout(), timeout=timeout
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
if not response_data:
|
|
301
|
+
return None
|
|
302
|
+
|
|
303
|
+
response = json.loads(response_data)
|
|
304
|
+
typed_response = t.cast(dict[str, t.Any], response)
|
|
305
|
+
|
|
306
|
+
# Check if this is the response we're looking for
|
|
307
|
+
if typed_response.get("id") == expected_id:
|
|
308
|
+
return typed_response
|
|
309
|
+
|
|
310
|
+
# Log if we got a different response
|
|
311
|
+
logger.debug(
|
|
312
|
+
f"Received response for ID {typed_response.get('id')}, expected {expected_id}"
|
|
313
|
+
)
|
|
314
|
+
return typed_response
|
|
315
|
+
|
|
316
|
+
except TimeoutError:
|
|
317
|
+
raise
|
|
318
|
+
except Exception as e:
|
|
319
|
+
logger.error(f"Failed to read LSP response: {e}")
|
|
320
|
+
return None
|
|
321
|
+
|
|
322
|
+
async def _read_message_from_stdout(self) -> str | None:
|
|
323
|
+
"""Read a complete LSP message from stdout.
|
|
324
|
+
|
|
325
|
+
Returns:
|
|
326
|
+
Message content as string, or None if failed
|
|
327
|
+
"""
|
|
328
|
+
if not self.process or not self.process.stdout:
|
|
329
|
+
return None
|
|
330
|
+
|
|
331
|
+
try:
|
|
332
|
+
# Read the Content-Length header
|
|
333
|
+
header_line = await self._read_line_async()
|
|
334
|
+
if not header_line or not header_line.startswith("Content-Length:"):
|
|
335
|
+
return None
|
|
336
|
+
|
|
337
|
+
# Extract content length
|
|
338
|
+
content_length = int(header_line.split(":", 1)[1].strip())
|
|
339
|
+
|
|
340
|
+
# Read the empty line separator
|
|
341
|
+
empty_line = await self._read_line_async()
|
|
342
|
+
if empty_line.strip(): # Should be empty
|
|
343
|
+
logger.warning("Expected empty line after Content-Length header")
|
|
344
|
+
|
|
345
|
+
# Read the JSON content
|
|
346
|
+
content_bytes = await self._read_bytes_async(content_length)
|
|
347
|
+
return content_bytes.decode("utf-8")
|
|
348
|
+
|
|
349
|
+
except Exception as e:
|
|
350
|
+
logger.error(f"Failed to read LSP message: {e}")
|
|
351
|
+
return None
|
|
352
|
+
|
|
353
|
+
async def _read_line_async(self) -> str:
|
|
354
|
+
"""Read a line from stdout asynchronously."""
|
|
355
|
+
if not self.process or not self.process.stdout:
|
|
356
|
+
return ""
|
|
357
|
+
|
|
358
|
+
# This is a simplified implementation
|
|
359
|
+
# In a production system, you'd want to use proper async I/O
|
|
360
|
+
loop = asyncio.get_event_loop()
|
|
361
|
+
line = await loop.run_in_executor(None, self.process.stdout.readline)
|
|
362
|
+
return line.decode("utf-8").rstrip("\r\n")
|
|
363
|
+
|
|
364
|
+
async def _read_bytes_async(self, count: int) -> bytes:
|
|
365
|
+
"""Read specified number of bytes from stdout asynchronously."""
|
|
366
|
+
if not self.process or not self.process.stdout:
|
|
367
|
+
return b""
|
|
368
|
+
|
|
369
|
+
loop = asyncio.get_event_loop()
|
|
370
|
+
data = await loop.run_in_executor(None, self.process.stdout.read, count)
|
|
371
|
+
return data
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
async def create_zuban_lsp_service(
|
|
375
|
+
port: int = 8677,
|
|
376
|
+
mode: str = "tcp",
|
|
377
|
+
console: Console | None = None,
|
|
378
|
+
) -> ZubanLSPService:
|
|
379
|
+
"""Factory function to create and optionally start Zuban LSP service.
|
|
380
|
+
|
|
381
|
+
Args:
|
|
382
|
+
port: TCP port for server (default: 8677)
|
|
383
|
+
mode: Transport mode, "tcp" or "stdio" (default: "tcp")
|
|
384
|
+
console: Rich console for output (optional)
|
|
385
|
+
|
|
386
|
+
Returns:
|
|
387
|
+
Configured ZubanLSPService instance
|
|
388
|
+
"""
|
|
389
|
+
service = ZubanLSPService(port=port, mode=mode, console=console)
|
|
390
|
+
return service
|
|
@@ -7,16 +7,13 @@ SLASH_COMMANDS_DIR = Path(__file__).parent
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
def get_slash_command_path(command_name: str) -> Path:
|
|
10
|
-
"""Get path to slash command file with validation."""
|
|
11
10
|
try:
|
|
12
|
-
# Validate command name to prevent directory traversal
|
|
13
11
|
sanitized_name = validate_and_sanitize_string(
|
|
14
12
|
command_name, max_length=50, strict_alphanumeric=True
|
|
15
13
|
)
|
|
16
14
|
|
|
17
15
|
command_path = SLASH_COMMANDS_DIR / f"{sanitized_name}.md"
|
|
18
16
|
|
|
19
|
-
# Ensure the path stays within the slash commands directory
|
|
20
17
|
if not str(command_path.resolve()).startswith(
|
|
21
18
|
str(SLASH_COMMANDS_DIR.resolve())
|
|
22
19
|
):
|
|
@@ -37,26 +34,24 @@ def get_slash_command_path(command_name: str) -> Path:
|
|
|
37
34
|
|
|
38
35
|
|
|
39
36
|
def list_available_commands() -> list[str]:
|
|
40
|
-
"""List available slash commands with validation."""
|
|
41
37
|
try:
|
|
42
38
|
commands = []
|
|
43
39
|
for file_path in SLASH_COMMANDS_DIR.glob("*.md"):
|
|
44
40
|
command_name = file_path.stem
|
|
45
|
-
|
|
41
|
+
|
|
46
42
|
try:
|
|
47
43
|
validate_and_sanitize_string(
|
|
48
44
|
command_name, max_length=50, strict_alphanumeric=True
|
|
49
45
|
)
|
|
50
46
|
commands.append(command_name)
|
|
51
47
|
except ExecutionError:
|
|
52
|
-
# Skip invalid command names
|
|
53
48
|
continue
|
|
54
49
|
|
|
55
50
|
return sorted(commands)
|
|
56
51
|
|
|
57
52
|
except Exception as e:
|
|
58
53
|
raise ExecutionError(
|
|
59
|
-
message="Failed to list available commands",
|
|
54
|
+
message="Failed to list[t.Any] available commands",
|
|
60
55
|
error_code=ErrorCode.FILE_READ_ERROR,
|
|
61
56
|
) from e
|
|
62
57
|
|
|
@@ -24,7 +24,7 @@ Run Crackerjack with advanced orchestrated AI-powered auto-fix mode to automatic
|
|
|
24
24
|
|
|
25
25
|
This slash command runs Crackerjack with AI agent mode for autonomous code quality enforcement:
|
|
26
26
|
|
|
27
|
-
- `--ai-
|
|
27
|
+
- `--ai-fix`: AI auto-fixing mode for structured error output and intelligent fixing
|
|
28
28
|
- `--test`: Run tests with comprehensive test coverage
|
|
29
29
|
- `--verbose`: Show detailed AI decision-making and execution details
|
|
30
30
|
|
|
@@ -104,7 +104,7 @@ AI: I'll use the /crackerjack:run command to automatically fix all code quality
|
|
|
104
104
|
|
|
105
105
|
/crackerjack:run
|
|
106
106
|
|
|
107
|
-
[AI runs: python -m crackerjack --ai-
|
|
107
|
+
[AI runs: python -m crackerjack --ai-fix --test --verbose]
|
|
108
108
|
|
|
109
109
|
The crackerjack AI agent completed successfully after 3 iterations! Here's what was automatically fixed:
|
|
110
110
|
|
|
@@ -1,34 +1,22 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Test script to validate that all new input validator SAFE_PATTERNS work correctly.
|
|
4
|
-
|
|
5
|
-
This script validates the security-critical input validation patterns and ensures
|
|
6
|
-
they provide proper protection against injection attacks and malicious input.
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
1
|
import sys
|
|
10
2
|
from pathlib import Path
|
|
11
3
|
|
|
12
|
-
# Add the crackerjack package to the path
|
|
13
4
|
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
14
5
|
|
|
15
6
|
from crackerjack.services.input_validator import SecureInputValidator
|
|
16
7
|
from crackerjack.services.regex_patterns import SAFE_PATTERNS
|
|
17
8
|
|
|
18
9
|
|
|
19
|
-
def test_sql_injection_patterns():
|
|
20
|
-
"""Test SQL injection detection patterns."""
|
|
10
|
+
def test_sql_injection_patterns() -> bool:
|
|
21
11
|
print("Testing SQL injection patterns...")
|
|
22
12
|
|
|
23
13
|
test_cases = [
|
|
24
|
-
# Should be detected as malicious
|
|
25
14
|
("SELECT * FROM users", True, "Basic SELECT"),
|
|
26
15
|
("UNION SELECT password FROM admin", True, "UNION injection"),
|
|
27
16
|
("'; DROP TABLE users; --", True, "SQL comment injection"),
|
|
28
17
|
("' OR 1=1--", True, "Boolean injection"),
|
|
29
18
|
("xp_cmdshell('dir')", True, "SQL Server specific"),
|
|
30
19
|
("sp_executesql @sql", True, "SQL Server procedure"),
|
|
31
|
-
# Should be allowed (legitimate text)
|
|
32
20
|
("user selected item", False, "Legitimate text with 'select'"),
|
|
33
21
|
("button execution", False, "Legitimate text with 'execution'"),
|
|
34
22
|
("team membership", False, "Legitimate text without SQL keywords"),
|
|
@@ -51,23 +39,21 @@ def test_sql_injection_patterns():
|
|
|
51
39
|
|
|
52
40
|
status = "✅" if detected == should_detect else "❌"
|
|
53
41
|
print(
|
|
54
|
-
f"
|
|
42
|
+
f" {status} {description}: '{text}' -> {'BLOCKED' if detected else 'ALLOWED'}"
|
|
55
43
|
)
|
|
56
44
|
|
|
57
45
|
if detected != should_detect:
|
|
58
|
-
print(f"
|
|
46
|
+
print(f" Expected: {'BLOCKED' if should_detect else 'ALLOWED'}")
|
|
59
47
|
return False
|
|
60
48
|
|
|
61
49
|
print("✅ All SQL injection pattern tests passed!")
|
|
62
50
|
return True
|
|
63
51
|
|
|
64
52
|
|
|
65
|
-
def test_code_injection_patterns():
|
|
66
|
-
"""Test code injection detection patterns."""
|
|
53
|
+
def test_code_injection_patterns() -> bool:
|
|
67
54
|
print("\nTesting code injection patterns...")
|
|
68
55
|
|
|
69
56
|
test_cases = [
|
|
70
|
-
# Should be detected as malicious
|
|
71
57
|
("eval(user_input)", True, "eval() execution"),
|
|
72
58
|
("exec(malicious_code)", True, "exec() execution"),
|
|
73
59
|
("__import__('os')", True, "Dynamic import"),
|
|
@@ -75,7 +61,6 @@ def test_code_injection_patterns():
|
|
|
75
61
|
("subprocess.run(cmd)", True, "System command"),
|
|
76
62
|
("os.system('rm -rf')", True, "OS system call"),
|
|
77
63
|
("compile(code, 'string', 'exec')", True, "Code compilation"),
|
|
78
|
-
# Should be allowed (legitimate text)
|
|
79
64
|
("evaluate the results", False, "Legitimate text with 'eval'"),
|
|
80
65
|
("execute the plan", False, "Legitimate text with 'execute'"),
|
|
81
66
|
("import statement", False, "Normal import discussion"),
|
|
@@ -98,30 +83,27 @@ def test_code_injection_patterns():
|
|
|
98
83
|
|
|
99
84
|
status = "✅" if detected == should_detect else "❌"
|
|
100
85
|
print(
|
|
101
|
-
f"
|
|
86
|
+
f" {status} {description}: '{text}' -> {'BLOCKED' if detected else 'ALLOWED'}"
|
|
102
87
|
)
|
|
103
88
|
|
|
104
89
|
if detected != should_detect:
|
|
105
|
-
print(f"
|
|
90
|
+
print(f" Expected: {'BLOCKED' if should_detect else 'ALLOWED'}")
|
|
106
91
|
return False
|
|
107
92
|
|
|
108
93
|
print("✅ All code injection pattern tests passed!")
|
|
109
94
|
return True
|
|
110
95
|
|
|
111
96
|
|
|
112
|
-
def test_job_id_validation():
|
|
113
|
-
"""Test job ID format validation."""
|
|
97
|
+
def test_job_id_validation() -> bool:
|
|
114
98
|
print("\nTesting job ID validation...")
|
|
115
99
|
|
|
116
100
|
test_cases = [
|
|
117
|
-
# Should be valid
|
|
118
101
|
("valid_job-123", True, "Standard job ID"),
|
|
119
102
|
("another-valid_job", True, "Hyphen and underscore"),
|
|
120
103
|
("JOB123", True, "Uppercase"),
|
|
121
104
|
("job_456", True, "Underscore only"),
|
|
122
105
|
("job-789", True, "Hyphen only"),
|
|
123
106
|
("complex_job-id_123", True, "Complex valid ID"),
|
|
124
|
-
# Should be invalid
|
|
125
107
|
("job with spaces", False, "Contains spaces"),
|
|
126
108
|
("job@invalid", False, "Contains @ symbol"),
|
|
127
109
|
("job.invalid", False, "Contains dot"),
|
|
@@ -137,29 +119,26 @@ def test_job_id_validation():
|
|
|
137
119
|
is_valid = pattern.test(job_id)
|
|
138
120
|
status = "✅" if is_valid == should_be_valid else "❌"
|
|
139
121
|
print(
|
|
140
|
-
f"
|
|
122
|
+
f" {status} {description}: '{job_id}' -> {'VALID' if is_valid else 'INVALID'}"
|
|
141
123
|
)
|
|
142
124
|
|
|
143
125
|
if is_valid != should_be_valid:
|
|
144
|
-
print(f"
|
|
126
|
+
print(f" Expected: {'VALID' if should_be_valid else 'INVALID'}")
|
|
145
127
|
return False
|
|
146
128
|
|
|
147
129
|
print("✅ All job ID validation tests passed!")
|
|
148
130
|
return True
|
|
149
131
|
|
|
150
132
|
|
|
151
|
-
def test_env_var_validation():
|
|
152
|
-
"""Test environment variable name validation."""
|
|
133
|
+
def test_env_var_validation() -> bool:
|
|
153
134
|
print("\nTesting environment variable name validation...")
|
|
154
135
|
|
|
155
136
|
test_cases = [
|
|
156
|
-
# Should be valid
|
|
157
137
|
("VALID_VAR", True, "Standard env var"),
|
|
158
138
|
("_PRIVATE_VAR", True, "Starting with underscore"),
|
|
159
139
|
("API_KEY_123", True, "With numbers"),
|
|
160
140
|
("DATABASE_URL", True, "Typical env var"),
|
|
161
141
|
("MAX_RETRIES", True, "Another typical var"),
|
|
162
|
-
# Should be invalid
|
|
163
142
|
("lowercase_var", False, "Contains lowercase"),
|
|
164
143
|
("123_INVALID", False, "Starts with number"),
|
|
165
144
|
("INVALID-VAR", False, "Contains hyphen"),
|
|
@@ -175,31 +154,28 @@ def test_env_var_validation():
|
|
|
175
154
|
is_valid = pattern.test(env_var)
|
|
176
155
|
status = "✅" if is_valid == should_be_valid else "❌"
|
|
177
156
|
print(
|
|
178
|
-
f"
|
|
157
|
+
f" {status} {description}: '{env_var}' -> {'VALID' if is_valid else 'INVALID'}"
|
|
179
158
|
)
|
|
180
159
|
|
|
181
160
|
if is_valid != should_be_valid:
|
|
182
|
-
print(f"
|
|
161
|
+
print(f" Expected: {'VALID' if should_be_valid else 'INVALID'}")
|
|
183
162
|
return False
|
|
184
163
|
|
|
185
164
|
print("✅ All environment variable validation tests passed!")
|
|
186
165
|
return True
|
|
187
166
|
|
|
188
167
|
|
|
189
|
-
def test_integration_with_validator():
|
|
190
|
-
"""Test integration with SecureInputValidator."""
|
|
168
|
+
def test_integration_with_validator() -> bool:
|
|
191
169
|
print("\nTesting integration with SecureInputValidator...")
|
|
192
170
|
|
|
193
171
|
validator = SecureInputValidator()
|
|
194
172
|
|
|
195
|
-
# Test SQL injection detection
|
|
196
173
|
result = validator.sanitizer.sanitize_string("'; DROP TABLE users; --")
|
|
197
174
|
if result.valid:
|
|
198
175
|
print("❌ SQL injection should have been detected")
|
|
199
176
|
return False
|
|
200
177
|
print("✅ SQL injection properly detected and blocked")
|
|
201
178
|
|
|
202
|
-
# Test job ID validation
|
|
203
179
|
result = validator.validate_job_id("valid_job-123")
|
|
204
180
|
if not result.valid:
|
|
205
181
|
print("❌ Valid job ID should have been accepted")
|
|
@@ -212,7 +188,6 @@ def test_integration_with_validator():
|
|
|
212
188
|
return False
|
|
213
189
|
print("✅ Invalid job ID properly rejected")
|
|
214
190
|
|
|
215
|
-
# Test environment variable validation
|
|
216
191
|
result = validator.validate_environment_var("VALID_VAR", "some_value")
|
|
217
192
|
if not result.valid:
|
|
218
193
|
print("❌ Valid env var should have been accepted")
|
|
@@ -229,8 +204,7 @@ def test_integration_with_validator():
|
|
|
229
204
|
return True
|
|
230
205
|
|
|
231
206
|
|
|
232
|
-
def main():
|
|
233
|
-
"""Run all validation tests."""
|
|
207
|
+
def main() -> int:
|
|
234
208
|
print("🔒 Validating Input Validator Security Patterns")
|
|
235
209
|
print("=" * 50)
|
|
236
210
|
|