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
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Bounded Status Operations to prevent DoS attacks.
|
|
3
|
-
|
|
4
|
-
Provides comprehensive operation limits, circuit breakers, and
|
|
5
|
-
resource protection for status collection operations.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
1
|
import asyncio
|
|
9
2
|
import time
|
|
10
3
|
import typing as t
|
|
@@ -17,17 +10,13 @@ from .security_logger import SecurityEventLevel, SecurityEventType, get_security
|
|
|
17
10
|
|
|
18
11
|
|
|
19
12
|
class OperationState(str, Enum):
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
OPEN = "open" # Circuit breaker tripped
|
|
24
|
-
HALF_OPEN = "half_open" # Testing if service recovered
|
|
13
|
+
CLOSED = "closed"
|
|
14
|
+
OPEN = "open"
|
|
15
|
+
HALF_OPEN = "half_open"
|
|
25
16
|
|
|
26
17
|
|
|
27
18
|
@dataclass
|
|
28
19
|
class OperationLimits:
|
|
29
|
-
"""Limits for status operations."""
|
|
30
|
-
|
|
31
20
|
max_concurrent_operations: int = 10
|
|
32
21
|
max_operations_per_minute: int = 60
|
|
33
22
|
max_operation_duration: float = 30.0
|
|
@@ -41,8 +30,6 @@ class OperationLimits:
|
|
|
41
30
|
|
|
42
31
|
@dataclass
|
|
43
32
|
class OperationMetrics:
|
|
44
|
-
"""Metrics for operation tracking."""
|
|
45
|
-
|
|
46
33
|
operation_id: str
|
|
47
34
|
operation_type: str
|
|
48
35
|
client_id: str
|
|
@@ -56,72 +43,46 @@ class OperationMetrics:
|
|
|
56
43
|
|
|
57
44
|
@property
|
|
58
45
|
def duration(self) -> float:
|
|
59
|
-
"""Get operation duration in seconds."""
|
|
60
46
|
end = self.end_time or time.time()
|
|
61
47
|
return end - self.start_time
|
|
62
48
|
|
|
63
49
|
@property
|
|
64
50
|
def is_completed(self) -> bool:
|
|
65
|
-
"""Check if operation is completed."""
|
|
66
51
|
return self.end_time is not None
|
|
67
52
|
|
|
68
53
|
|
|
69
54
|
class OperationLimitExceededError(Exception):
|
|
70
|
-
"""Raised when operation limits are exceeded."""
|
|
71
|
-
|
|
72
55
|
pass
|
|
73
56
|
|
|
74
57
|
|
|
75
58
|
class CircuitBreakerOpenError(Exception):
|
|
76
|
-
"""Raised when circuit breaker is open."""
|
|
77
|
-
|
|
78
59
|
pass
|
|
79
60
|
|
|
80
61
|
|
|
81
62
|
class BoundedStatusOperations:
|
|
82
|
-
"""
|
|
83
|
-
Bounded operations manager for status collection.
|
|
84
|
-
|
|
85
|
-
Features:
|
|
86
|
-
- Concurrent operation limits
|
|
87
|
-
- Rate limiting per client
|
|
88
|
-
- Memory and CPU usage monitoring
|
|
89
|
-
- Circuit breaker pattern for fault tolerance
|
|
90
|
-
- Operation timeout enforcement
|
|
91
|
-
- Comprehensive metrics collection
|
|
92
|
-
"""
|
|
93
|
-
|
|
94
63
|
def __init__(self, limits: OperationLimits | None = None):
|
|
95
|
-
"""
|
|
96
|
-
Initialize bounded operations manager.
|
|
97
|
-
|
|
98
|
-
Args:
|
|
99
|
-
limits: Operation limits configuration
|
|
100
|
-
"""
|
|
101
64
|
self.limits = limits or OperationLimits()
|
|
102
65
|
self.security_logger = get_security_logger()
|
|
103
66
|
|
|
104
|
-
# Thread-safe operation tracking
|
|
105
67
|
self._lock = RLock()
|
|
106
68
|
self._active_operations: dict[str, OperationMetrics] = {}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
69
|
+
|
|
70
|
+
def _create_deque() -> deque[float]:
|
|
71
|
+
return deque(maxlen=100)
|
|
72
|
+
|
|
73
|
+
self._client_operations: dict[str, deque[float]] = defaultdict(_create_deque)
|
|
110
74
|
self._operation_history: list[OperationMetrics] = []
|
|
111
75
|
|
|
112
|
-
# Circuit breaker tracking
|
|
113
76
|
self._circuit_states: dict[str, OperationState] = defaultdict(
|
|
114
|
-
lambda: OperationState.CLOSED
|
|
77
|
+
lambda: OperationState.CLOSED # type: ignore[misc]
|
|
115
78
|
)
|
|
116
79
|
self._failure_counts: dict[str, int] = defaultdict(int)
|
|
117
80
|
self._last_failure_times: dict[str, float] = {}
|
|
118
81
|
|
|
119
|
-
# Resource usage tracking
|
|
120
82
|
self._total_memory_usage = 0
|
|
121
83
|
self._total_cpu_time = 0.0
|
|
122
84
|
self._total_file_operations = 0
|
|
123
85
|
|
|
124
|
-
# Operation type definitions
|
|
125
86
|
self._operation_types = {
|
|
126
87
|
"collect_status": "Status collection operation",
|
|
127
88
|
"get_jobs": "Job information retrieval",
|
|
@@ -138,32 +99,10 @@ class BoundedStatusOperations:
|
|
|
138
99
|
*args: t.Any,
|
|
139
100
|
**kwargs: t.Any,
|
|
140
101
|
) -> t.Any:
|
|
141
|
-
"""
|
|
142
|
-
Execute a bounded status operation with comprehensive limits.
|
|
143
|
-
|
|
144
|
-
Args:
|
|
145
|
-
operation_type: Type of operation being performed
|
|
146
|
-
client_id: Client identifier
|
|
147
|
-
operation_func: Async function to execute
|
|
148
|
-
*args: Positional arguments for operation_func
|
|
149
|
-
**kwargs: Keyword arguments for operation_func
|
|
150
|
-
|
|
151
|
-
Returns:
|
|
152
|
-
Result of the operation
|
|
153
|
-
|
|
154
|
-
Raises:
|
|
155
|
-
OperationLimitExceededError: If operation limits exceeded
|
|
156
|
-
CircuitBreakerOpenError: If circuit breaker is open
|
|
157
|
-
asyncio.TimeoutError: If operation times out
|
|
158
|
-
"""
|
|
159
|
-
|
|
160
|
-
# Check circuit breaker
|
|
161
102
|
self._check_circuit_breaker(operation_type)
|
|
162
103
|
|
|
163
|
-
# Validate operation can be started
|
|
164
104
|
operation_id = self._validate_and_reserve_operation(operation_type, client_id)
|
|
165
105
|
|
|
166
|
-
# Create operation metrics
|
|
167
106
|
metrics = OperationMetrics(
|
|
168
107
|
operation_id=operation_id,
|
|
169
108
|
operation_type=operation_type,
|
|
@@ -171,23 +110,18 @@ class BoundedStatusOperations:
|
|
|
171
110
|
)
|
|
172
111
|
|
|
173
112
|
try:
|
|
174
|
-
# Register operation
|
|
175
113
|
with self._lock:
|
|
176
114
|
self._active_operations[operation_id] = metrics
|
|
177
115
|
|
|
178
|
-
# Execute with timeout and resource monitoring
|
|
179
116
|
result = await self._execute_with_monitoring(
|
|
180
117
|
operation_func, metrics, *args, **kwargs
|
|
181
118
|
)
|
|
182
119
|
|
|
183
|
-
# Mark as successful
|
|
184
120
|
metrics.success = True
|
|
185
121
|
metrics.end_time = time.time()
|
|
186
122
|
|
|
187
|
-
# Update circuit breaker on success
|
|
188
123
|
self._record_operation_success(operation_type)
|
|
189
124
|
|
|
190
|
-
# Log successful operation
|
|
191
125
|
self.security_logger.log_security_event(
|
|
192
126
|
event_type=SecurityEventType.OPERATION_SUCCESS,
|
|
193
127
|
level=SecurityEventLevel.LOW,
|
|
@@ -206,15 +140,12 @@ class BoundedStatusOperations:
|
|
|
206
140
|
return result
|
|
207
141
|
|
|
208
142
|
except Exception as e:
|
|
209
|
-
# Mark as failed
|
|
210
143
|
metrics.success = False
|
|
211
144
|
metrics.end_time = time.time()
|
|
212
145
|
metrics.error_message = str(e)
|
|
213
146
|
|
|
214
|
-
# Update circuit breaker on failure
|
|
215
147
|
self._record_operation_failure(operation_type)
|
|
216
148
|
|
|
217
|
-
# Log failed operation
|
|
218
149
|
self.security_logger.log_security_event(
|
|
219
150
|
event_type=SecurityEventType.OPERATION_FAILURE,
|
|
220
151
|
level=SecurityEventLevel.HIGH,
|
|
@@ -231,12 +162,9 @@ class BoundedStatusOperations:
|
|
|
231
162
|
raise
|
|
232
163
|
|
|
233
164
|
finally:
|
|
234
|
-
# Clean up operation
|
|
235
165
|
self._cleanup_operation(operation_id, metrics)
|
|
236
166
|
|
|
237
167
|
def _check_circuit_breaker(self, operation_type: str) -> None:
|
|
238
|
-
"""Check if circuit breaker allows operation."""
|
|
239
|
-
|
|
240
168
|
current_time = time.time()
|
|
241
169
|
|
|
242
170
|
with self._lock:
|
|
@@ -245,9 +173,7 @@ class BoundedStatusOperations:
|
|
|
245
173
|
last_failure = self._last_failure_times.get(operation_type, 0)
|
|
246
174
|
|
|
247
175
|
if state == OperationState.OPEN:
|
|
248
|
-
# Check if circuit breaker timeout has passed
|
|
249
176
|
if current_time - last_failure >= self.limits.circuit_breaker_timeout:
|
|
250
|
-
# Move to half-open state for testing
|
|
251
177
|
self._circuit_states[operation_type] = OperationState.HALF_OPEN
|
|
252
178
|
self.security_logger.log_security_event(
|
|
253
179
|
event_type=SecurityEventType.CIRCUIT_BREAKER_HALF_OPEN,
|
|
@@ -256,7 +182,6 @@ class BoundedStatusOperations:
|
|
|
256
182
|
operation=operation_type,
|
|
257
183
|
)
|
|
258
184
|
else:
|
|
259
|
-
# Circuit breaker still open
|
|
260
185
|
self.security_logger.log_security_event(
|
|
261
186
|
event_type=SecurityEventType.CIRCUIT_BREAKER_OPEN,
|
|
262
187
|
level=SecurityEventLevel.MEDIUM,
|
|
@@ -275,13 +200,10 @@ class BoundedStatusOperations:
|
|
|
275
200
|
def _validate_and_reserve_operation(
|
|
276
201
|
self, operation_type: str, client_id: str
|
|
277
202
|
) -> str:
|
|
278
|
-
"""Validate operation can be started and reserve resources."""
|
|
279
|
-
|
|
280
203
|
current_time = time.time()
|
|
281
204
|
operation_id = f"{operation_type}_{client_id}_{int(current_time * 1000)}"
|
|
282
205
|
|
|
283
206
|
with self._lock:
|
|
284
|
-
# Check concurrent operation limit
|
|
285
207
|
if len(self._active_operations) >= self.limits.max_concurrent_operations:
|
|
286
208
|
self.security_logger.log_security_event(
|
|
287
209
|
event_type=SecurityEventType.RATE_LIMIT_EXCEEDED,
|
|
@@ -294,10 +216,8 @@ class BoundedStatusOperations:
|
|
|
294
216
|
f"Maximum concurrent operations exceeded: {len(self._active_operations)}"
|
|
295
217
|
)
|
|
296
218
|
|
|
297
|
-
# Check client rate limiting
|
|
298
219
|
client_ops = self._client_operations[client_id]
|
|
299
220
|
|
|
300
|
-
# Remove operations older than 1 minute
|
|
301
221
|
while client_ops and current_time - client_ops[0] > 60.0:
|
|
302
222
|
client_ops.popleft()
|
|
303
223
|
|
|
@@ -313,7 +233,6 @@ class BoundedStatusOperations:
|
|
|
313
233
|
f"Operation rate limit exceeded: {len(client_ops)} operations/min"
|
|
314
234
|
)
|
|
315
235
|
|
|
316
|
-
# Check resource usage limits
|
|
317
236
|
if (
|
|
318
237
|
self._total_memory_usage
|
|
319
238
|
>= self.limits.max_memory_usage_mb * 1024 * 1024
|
|
@@ -321,15 +240,14 @@ class BoundedStatusOperations:
|
|
|
321
240
|
self.security_logger.log_security_event(
|
|
322
241
|
event_type=SecurityEventType.RESOURCE_EXHAUSTED,
|
|
323
242
|
level=SecurityEventLevel.HIGH,
|
|
324
|
-
message=f"Memory limit exceeded: {self._total_memory_usage / 1024 / 1024
|
|
243
|
+
message=f"Memory limit exceeded: {self._total_memory_usage / 1024 / 1024: .1f}MB",
|
|
325
244
|
client_id=client_id,
|
|
326
245
|
operation=operation_type,
|
|
327
246
|
)
|
|
328
247
|
raise OperationLimitExceededError(
|
|
329
|
-
f"Memory limit exceeded: {self._total_memory_usage / 1024 / 1024
|
|
248
|
+
f"Memory limit exceeded: {self._total_memory_usage / 1024 / 1024: .1f}MB"
|
|
330
249
|
)
|
|
331
250
|
|
|
332
|
-
# Record operation start
|
|
333
251
|
client_ops.append(current_time)
|
|
334
252
|
|
|
335
253
|
return operation_id
|
|
@@ -341,13 +259,9 @@ class BoundedStatusOperations:
|
|
|
341
259
|
*args: t.Any,
|
|
342
260
|
**kwargs: t.Any,
|
|
343
261
|
) -> t.Any:
|
|
344
|
-
"""Execute operation with resource monitoring and timeout."""
|
|
345
|
-
|
|
346
|
-
# Create monitoring task
|
|
347
262
|
monitor_task = asyncio.create_task(self._monitor_operation(metrics))
|
|
348
263
|
|
|
349
264
|
try:
|
|
350
|
-
# Execute operation with timeout
|
|
351
265
|
result = await asyncio.wait_for(
|
|
352
266
|
operation_func(*args, **kwargs),
|
|
353
267
|
timeout=self.limits.timeout_seconds,
|
|
@@ -372,7 +286,6 @@ class BoundedStatusOperations:
|
|
|
372
286
|
)
|
|
373
287
|
|
|
374
288
|
finally:
|
|
375
|
-
# Cancel monitoring
|
|
376
289
|
monitor_task.cancel()
|
|
377
290
|
try:
|
|
378
291
|
await monitor_task
|
|
@@ -380,8 +293,6 @@ class BoundedStatusOperations:
|
|
|
380
293
|
pass
|
|
381
294
|
|
|
382
295
|
async def _monitor_operation(self, metrics: OperationMetrics) -> None:
|
|
383
|
-
"""Monitor operation resource usage."""
|
|
384
|
-
|
|
385
296
|
import os
|
|
386
297
|
|
|
387
298
|
import psutil
|
|
@@ -392,21 +303,18 @@ class BoundedStatusOperations:
|
|
|
392
303
|
|
|
393
304
|
while not metrics.is_completed:
|
|
394
305
|
try:
|
|
395
|
-
# Update CPU time
|
|
396
306
|
current_cpu_time = (
|
|
397
307
|
process.cpu_times().user + process.cpu_times().system
|
|
398
308
|
)
|
|
399
309
|
metrics.cpu_time = current_cpu_time - initial_cpu_time
|
|
400
310
|
|
|
401
|
-
# Update memory usage
|
|
402
311
|
metrics.memory_usage = process.memory_info().rss
|
|
403
312
|
|
|
404
|
-
# Check limits
|
|
405
313
|
if metrics.cpu_time > self.limits.max_cpu_time_seconds:
|
|
406
314
|
self.security_logger.log_security_event(
|
|
407
315
|
event_type=SecurityEventType.RESOURCE_LIMIT_EXCEEDED,
|
|
408
316
|
level=SecurityEventLevel.MEDIUM,
|
|
409
|
-
message=f"CPU time limit exceeded: {metrics.cpu_time
|
|
317
|
+
message=f"CPU time limit exceeded: {metrics.cpu_time: .2f}s",
|
|
410
318
|
client_id=metrics.client_id,
|
|
411
319
|
operation=metrics.operation_type,
|
|
412
320
|
additional_data={"operation_id": metrics.operation_id},
|
|
@@ -417,20 +325,19 @@ class BoundedStatusOperations:
|
|
|
417
325
|
self.security_logger.log_security_event(
|
|
418
326
|
event_type=SecurityEventType.OPERATION_DURATION_EXCEEDED,
|
|
419
327
|
level=SecurityEventLevel.MEDIUM,
|
|
420
|
-
message=f"Operation duration limit exceeded: {metrics.duration
|
|
328
|
+
message=f"Operation duration limit exceeded: {metrics.duration: .2f}s",
|
|
421
329
|
client_id=metrics.client_id,
|
|
422
330
|
operation=metrics.operation_type,
|
|
423
331
|
additional_data={"operation_id": metrics.operation_id},
|
|
424
332
|
)
|
|
425
333
|
break
|
|
426
334
|
|
|
427
|
-
await asyncio.sleep(0.1)
|
|
335
|
+
await asyncio.sleep(0.1)
|
|
428
336
|
|
|
429
337
|
except psutil.NoSuchProcess:
|
|
430
338
|
break
|
|
431
339
|
|
|
432
340
|
except Exception as e:
|
|
433
|
-
# Monitoring failure shouldn't stop the operation
|
|
434
341
|
self.security_logger.log_security_event(
|
|
435
342
|
event_type=SecurityEventType.MONITORING_ERROR,
|
|
436
343
|
level=SecurityEventLevel.MEDIUM,
|
|
@@ -440,13 +347,10 @@ class BoundedStatusOperations:
|
|
|
440
347
|
)
|
|
441
348
|
|
|
442
349
|
def _record_operation_success(self, operation_type: str) -> None:
|
|
443
|
-
"""Record successful operation for circuit breaker."""
|
|
444
|
-
|
|
445
350
|
with self._lock:
|
|
446
351
|
state = self._circuit_states[operation_type]
|
|
447
352
|
|
|
448
353
|
if state == OperationState.HALF_OPEN:
|
|
449
|
-
# Success in half-open state - close circuit breaker
|
|
450
354
|
self._circuit_states[operation_type] = OperationState.CLOSED
|
|
451
355
|
self._failure_counts[operation_type] = 0
|
|
452
356
|
|
|
@@ -457,12 +361,9 @@ class BoundedStatusOperations:
|
|
|
457
361
|
operation=operation_type,
|
|
458
362
|
)
|
|
459
363
|
elif state == OperationState.CLOSED:
|
|
460
|
-
# Reset failure count on success
|
|
461
364
|
self._failure_counts[operation_type] = 0
|
|
462
365
|
|
|
463
366
|
def _record_operation_failure(self, operation_type: str) -> None:
|
|
464
|
-
"""Record failed operation for circuit breaker."""
|
|
465
|
-
|
|
466
367
|
current_time = time.time()
|
|
467
368
|
|
|
468
369
|
with self._lock:
|
|
@@ -474,7 +375,6 @@ class BoundedStatusOperations:
|
|
|
474
375
|
|
|
475
376
|
if failure_count >= self.limits.circuit_breaker_threshold:
|
|
476
377
|
if state != OperationState.OPEN:
|
|
477
|
-
# Trip circuit breaker
|
|
478
378
|
self._circuit_states[operation_type] = OperationState.OPEN
|
|
479
379
|
|
|
480
380
|
self.security_logger.log_security_event(
|
|
@@ -489,19 +389,14 @@ class BoundedStatusOperations:
|
|
|
489
389
|
)
|
|
490
390
|
|
|
491
391
|
def _cleanup_operation(self, operation_id: str, metrics: OperationMetrics) -> None:
|
|
492
|
-
"""Clean up operation and update metrics."""
|
|
493
|
-
|
|
494
392
|
with self._lock:
|
|
495
|
-
# Remove from active operations
|
|
496
393
|
if operation_id in self._active_operations:
|
|
497
394
|
del self._active_operations[operation_id]
|
|
498
395
|
|
|
499
|
-
# Add to history (keep last 1000 operations)
|
|
500
396
|
self._operation_history.append(metrics)
|
|
501
397
|
if len(self._operation_history) > 1000:
|
|
502
398
|
self._operation_history.pop(0)
|
|
503
399
|
|
|
504
|
-
# Update resource usage tracking
|
|
505
400
|
self._total_memory_usage = max(
|
|
506
401
|
0, self._total_memory_usage - metrics.memory_usage
|
|
507
402
|
)
|
|
@@ -509,16 +404,11 @@ class BoundedStatusOperations:
|
|
|
509
404
|
self._total_file_operations += metrics.file_operations
|
|
510
405
|
|
|
511
406
|
def get_operation_status(self) -> dict[str, t.Any]:
|
|
512
|
-
"""Get current operation status and limits."""
|
|
513
|
-
|
|
514
407
|
with self._lock:
|
|
515
408
|
current_time = time.time()
|
|
516
409
|
|
|
517
|
-
# Calculate recent operation stats
|
|
518
410
|
recent_ops = [
|
|
519
|
-
m
|
|
520
|
-
for m in self._operation_history
|
|
521
|
-
if current_time - m.start_time < 300 # Last 5 minutes
|
|
411
|
+
m for m in self._operation_history if current_time - m.start_time < 300
|
|
522
412
|
]
|
|
523
413
|
|
|
524
414
|
successful_ops = [m for m in recent_ops if m.success is True]
|
|
@@ -558,16 +448,6 @@ class BoundedStatusOperations:
|
|
|
558
448
|
}
|
|
559
449
|
|
|
560
450
|
def reset_circuit_breaker(self, operation_type: str) -> bool:
|
|
561
|
-
"""
|
|
562
|
-
Manually reset a circuit breaker.
|
|
563
|
-
|
|
564
|
-
Args:
|
|
565
|
-
operation_type: Type of operation
|
|
566
|
-
|
|
567
|
-
Returns:
|
|
568
|
-
True if circuit breaker was reset
|
|
569
|
-
"""
|
|
570
|
-
|
|
571
451
|
with self._lock:
|
|
572
452
|
if operation_type in self._circuit_states:
|
|
573
453
|
self._circuit_states[operation_type] = OperationState.CLOSED
|
|
@@ -585,15 +465,12 @@ class BoundedStatusOperations:
|
|
|
585
465
|
return False
|
|
586
466
|
|
|
587
467
|
|
|
588
|
-
# Global singleton instance
|
|
589
468
|
_bounded_operations: BoundedStatusOperations | None = None
|
|
590
469
|
|
|
591
470
|
|
|
592
471
|
def get_bounded_status_operations(
|
|
593
472
|
limits: OperationLimits | None = None,
|
|
594
473
|
) -> BoundedStatusOperations:
|
|
595
|
-
"""Get the global bounded status operations instance."""
|
|
596
|
-
|
|
597
474
|
global _bounded_operations
|
|
598
475
|
if _bounded_operations is None:
|
|
599
476
|
_bounded_operations = BoundedStatusOperations(limits)
|
|
@@ -607,20 +484,6 @@ async def execute_bounded_status_operation(
|
|
|
607
484
|
*args: t.Any,
|
|
608
485
|
**kwargs: t.Any,
|
|
609
486
|
) -> t.Any:
|
|
610
|
-
"""
|
|
611
|
-
Convenience function for bounded operation execution.
|
|
612
|
-
|
|
613
|
-
Args:
|
|
614
|
-
operation_type: Type of operation
|
|
615
|
-
client_id: Client identifier
|
|
616
|
-
operation_func: Async function to execute
|
|
617
|
-
*args: Positional arguments
|
|
618
|
-
**kwargs: Keyword arguments
|
|
619
|
-
|
|
620
|
-
Returns:
|
|
621
|
-
Operation result
|
|
622
|
-
"""
|
|
623
|
-
|
|
624
487
|
operations_manager = get_bounded_status_operations()
|
|
625
488
|
return await operations_manager.execute_bounded_operation(
|
|
626
489
|
operation_type, client_id, operation_func, *args, **kwargs
|
crackerjack/services/cache.py
CHANGED
|
@@ -220,12 +220,41 @@ class FileCache:
|
|
|
220
220
|
|
|
221
221
|
|
|
222
222
|
class CrackerjackCache:
|
|
223
|
+
# Expensive hooks that benefit from disk caching across sessions
|
|
224
|
+
EXPENSIVE_HOOKS = {
|
|
225
|
+
"pyright",
|
|
226
|
+
"bandit",
|
|
227
|
+
"vulture",
|
|
228
|
+
"complexipy",
|
|
229
|
+
"refurb",
|
|
230
|
+
"gitleaks",
|
|
231
|
+
"detect-secrets",
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
# TTL configuration for different cache types (in seconds)
|
|
235
|
+
HOOK_DISK_TTLS = {
|
|
236
|
+
"pyright": 86400, # 24 hours - type checking is stable
|
|
237
|
+
"bandit": 86400 * 3, # 3 days - security patterns change slowly
|
|
238
|
+
"vulture": 86400 * 2, # 2 days - dead code detection is stable
|
|
239
|
+
"complexipy": 86400, # 24 hours - complexity analysis
|
|
240
|
+
"refurb": 86400, # 24 hours - code improvements
|
|
241
|
+
"gitleaks": 86400 * 7, # 7 days - secret detection is very stable
|
|
242
|
+
"detect-secrets": 86400 * 7, # 7 days - secret detection
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
# Agent version for cache invalidation when agent logic changes
|
|
246
|
+
AGENT_VERSION = "1.0.0"
|
|
247
|
+
|
|
223
248
|
def __init__(
|
|
224
249
|
self,
|
|
225
250
|
cache_dir: Path | None = None,
|
|
226
251
|
enable_disk_cache: bool = True,
|
|
227
252
|
) -> None:
|
|
228
|
-
|
|
253
|
+
if cache_dir:
|
|
254
|
+
self.cache_dir = cache_dir
|
|
255
|
+
else:
|
|
256
|
+
self.cache_dir = Path.cwd() / ".crackerjack" / "cache"
|
|
257
|
+
|
|
229
258
|
self.enable_disk_cache = enable_disk_cache
|
|
230
259
|
|
|
231
260
|
self.hook_results_cache = InMemoryCache(max_entries=500, default_ttl=1800)
|
|
@@ -252,6 +281,46 @@ class CrackerjackCache:
|
|
|
252
281
|
cache_key = self._get_hook_cache_key(hook_name, file_hashes)
|
|
253
282
|
self.hook_results_cache.set(cache_key, result, ttl_seconds=1800)
|
|
254
283
|
|
|
284
|
+
def get_expensive_hook_result(
|
|
285
|
+
self,
|
|
286
|
+
hook_name: str,
|
|
287
|
+
file_hashes: list[str],
|
|
288
|
+
tool_version: str | None = None,
|
|
289
|
+
) -> HookResult | None:
|
|
290
|
+
"""Get hook result with disk cache fallback for expensive hooks."""
|
|
291
|
+
# Always check memory first for speed
|
|
292
|
+
result = self.get_hook_result(hook_name, file_hashes)
|
|
293
|
+
if result:
|
|
294
|
+
return result
|
|
295
|
+
|
|
296
|
+
# Fall back to disk cache for expensive hooks
|
|
297
|
+
if self.enable_disk_cache and hook_name in self.EXPENSIVE_HOOKS:
|
|
298
|
+
cache_key = self._get_versioned_hook_cache_key(
|
|
299
|
+
hook_name, file_hashes, tool_version
|
|
300
|
+
)
|
|
301
|
+
return self.disk_cache.get(cache_key)
|
|
302
|
+
|
|
303
|
+
return None
|
|
304
|
+
|
|
305
|
+
def set_expensive_hook_result(
|
|
306
|
+
self,
|
|
307
|
+
hook_name: str,
|
|
308
|
+
file_hashes: list[str],
|
|
309
|
+
result: HookResult,
|
|
310
|
+
tool_version: str | None = None,
|
|
311
|
+
) -> None:
|
|
312
|
+
"""Set hook result in both memory and disk cache for expensive hooks."""
|
|
313
|
+
# Always set[t.Any] in memory for current session
|
|
314
|
+
self.set_hook_result(hook_name, file_hashes, result)
|
|
315
|
+
|
|
316
|
+
# Also persist to disk for expensive hooks
|
|
317
|
+
if self.enable_disk_cache and hook_name in self.EXPENSIVE_HOOKS:
|
|
318
|
+
cache_key = self._get_versioned_hook_cache_key(
|
|
319
|
+
hook_name, file_hashes, tool_version
|
|
320
|
+
)
|
|
321
|
+
ttl = self.HOOK_DISK_TTLS.get(hook_name, 86400) # Default 24 hours
|
|
322
|
+
self.disk_cache.set(cache_key, result, ttl_seconds=ttl)
|
|
323
|
+
|
|
255
324
|
def get_file_hash(self, file_path: Path) -> str | None:
|
|
256
325
|
stat = file_path.stat()
|
|
257
326
|
cache_key = f"file_hash: {file_path}: {stat.st_mtime}: {stat.st_size}"
|
|
@@ -268,6 +337,49 @@ class CrackerjackCache:
|
|
|
268
337
|
def set_config_data(self, config_key: str, data: t.Any) -> None:
|
|
269
338
|
self.config_cache.set(f"config: {config_key}", data, ttl_seconds=7200)
|
|
270
339
|
|
|
340
|
+
def get(self, key: str, default: t.Any = None) -> t.Any:
|
|
341
|
+
"""General purpose get method for metrics and other data."""
|
|
342
|
+
return self.config_cache.get(key) or default
|
|
343
|
+
|
|
344
|
+
def set(self, key: str, value: t.Any, ttl_seconds: int | None = None) -> None:
|
|
345
|
+
"""General purpose set method for metrics and other data."""
|
|
346
|
+
ttl = ttl_seconds or 3600 # Default 1 hour
|
|
347
|
+
self.config_cache.set(key, value, ttl_seconds=ttl)
|
|
348
|
+
|
|
349
|
+
def get_agent_decision(self, agent_name: str, issue_hash: str) -> t.Any | None:
|
|
350
|
+
"""Get cached AI agent decision based on issue content."""
|
|
351
|
+
if not self.enable_disk_cache:
|
|
352
|
+
return None
|
|
353
|
+
|
|
354
|
+
cache_key = f"agent:{agent_name}:{issue_hash}:{self.AGENT_VERSION}"
|
|
355
|
+
return self.disk_cache.get(cache_key)
|
|
356
|
+
|
|
357
|
+
def set_agent_decision(
|
|
358
|
+
self, agent_name: str, issue_hash: str, decision: t.Any
|
|
359
|
+
) -> None:
|
|
360
|
+
"""Cache AI agent decision for future use."""
|
|
361
|
+
if not self.enable_disk_cache:
|
|
362
|
+
return
|
|
363
|
+
|
|
364
|
+
cache_key = f"agent:{agent_name}:{issue_hash}:{self.AGENT_VERSION}"
|
|
365
|
+
self.disk_cache.set(cache_key, decision, ttl_seconds=604800) # 7 days
|
|
366
|
+
|
|
367
|
+
def get_quality_baseline(self, git_hash: str) -> dict[str, t.Any] | None:
|
|
368
|
+
"""Get quality baseline metrics for a specific git commit."""
|
|
369
|
+
if not self.enable_disk_cache:
|
|
370
|
+
return None
|
|
371
|
+
|
|
372
|
+
return self.disk_cache.get(f"baseline:{git_hash}")
|
|
373
|
+
|
|
374
|
+
def set_quality_baseline(self, git_hash: str, metrics: dict[str, t.Any]) -> None:
|
|
375
|
+
"""Store quality baseline metrics for a git commit."""
|
|
376
|
+
if not self.enable_disk_cache:
|
|
377
|
+
return
|
|
378
|
+
|
|
379
|
+
self.disk_cache.set(
|
|
380
|
+
f"baseline:{git_hash}", metrics, ttl_seconds=2592000
|
|
381
|
+
) # 30 days
|
|
382
|
+
|
|
271
383
|
def invalidate_hook_cache(self, hook_name: str | None = None) -> None:
|
|
272
384
|
if hook_name:
|
|
273
385
|
keys_to_remove = [
|
|
@@ -310,3 +422,17 @@ class CrackerjackCache:
|
|
|
310
422
|
usedforsecurity=False,
|
|
311
423
|
).hexdigest()
|
|
312
424
|
return f"hook_result: {hook_name}: {hash_signature}"
|
|
425
|
+
|
|
426
|
+
def _get_versioned_hook_cache_key(
|
|
427
|
+
self,
|
|
428
|
+
hook_name: str,
|
|
429
|
+
file_hashes: list[str],
|
|
430
|
+
tool_version: str | None = None,
|
|
431
|
+
) -> str:
|
|
432
|
+
"""Get cache key with tool version for disk cache invalidation."""
|
|
433
|
+
hash_signature = hashlib.md5(
|
|
434
|
+
", ".join(sorted(file_hashes)).encode(),
|
|
435
|
+
usedforsecurity=False,
|
|
436
|
+
).hexdigest()
|
|
437
|
+
version_part = f":{tool_version}" if tool_version else ""
|
|
438
|
+
return f"hook_result: {hook_name}: {hash_signature}{version_part}"
|