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.

Files changed (200) hide show
  1. crackerjack/__main__.py +1350 -34
  2. crackerjack/adapters/__init__.py +17 -0
  3. crackerjack/adapters/lsp_client.py +358 -0
  4. crackerjack/adapters/rust_tool_adapter.py +194 -0
  5. crackerjack/adapters/rust_tool_manager.py +193 -0
  6. crackerjack/adapters/skylos_adapter.py +231 -0
  7. crackerjack/adapters/zuban_adapter.py +560 -0
  8. crackerjack/agents/base.py +7 -3
  9. crackerjack/agents/coordinator.py +271 -33
  10. crackerjack/agents/documentation_agent.py +9 -15
  11. crackerjack/agents/dry_agent.py +3 -15
  12. crackerjack/agents/formatting_agent.py +1 -1
  13. crackerjack/agents/import_optimization_agent.py +36 -180
  14. crackerjack/agents/performance_agent.py +17 -98
  15. crackerjack/agents/performance_helpers.py +7 -31
  16. crackerjack/agents/proactive_agent.py +1 -3
  17. crackerjack/agents/refactoring_agent.py +16 -85
  18. crackerjack/agents/refactoring_helpers.py +7 -42
  19. crackerjack/agents/security_agent.py +9 -48
  20. crackerjack/agents/test_creation_agent.py +356 -513
  21. crackerjack/agents/test_specialist_agent.py +0 -4
  22. crackerjack/api.py +6 -25
  23. crackerjack/cli/cache_handlers.py +204 -0
  24. crackerjack/cli/cache_handlers_enhanced.py +683 -0
  25. crackerjack/cli/facade.py +100 -0
  26. crackerjack/cli/handlers.py +224 -9
  27. crackerjack/cli/interactive.py +6 -4
  28. crackerjack/cli/options.py +642 -55
  29. crackerjack/cli/utils.py +2 -1
  30. crackerjack/code_cleaner.py +58 -117
  31. crackerjack/config/global_lock_config.py +8 -48
  32. crackerjack/config/hooks.py +53 -62
  33. crackerjack/core/async_workflow_orchestrator.py +24 -34
  34. crackerjack/core/autofix_coordinator.py +3 -17
  35. crackerjack/core/enhanced_container.py +64 -6
  36. crackerjack/core/file_lifecycle.py +12 -89
  37. crackerjack/core/performance.py +2 -2
  38. crackerjack/core/performance_monitor.py +15 -55
  39. crackerjack/core/phase_coordinator.py +257 -218
  40. crackerjack/core/resource_manager.py +14 -90
  41. crackerjack/core/service_watchdog.py +62 -95
  42. crackerjack/core/session_coordinator.py +149 -0
  43. crackerjack/core/timeout_manager.py +14 -72
  44. crackerjack/core/websocket_lifecycle.py +13 -78
  45. crackerjack/core/workflow_orchestrator.py +558 -240
  46. crackerjack/docs/INDEX.md +11 -0
  47. crackerjack/docs/generated/api/API_REFERENCE.md +10895 -0
  48. crackerjack/docs/generated/api/CLI_REFERENCE.md +109 -0
  49. crackerjack/docs/generated/api/CROSS_REFERENCES.md +1755 -0
  50. crackerjack/docs/generated/api/PROTOCOLS.md +3 -0
  51. crackerjack/docs/generated/api/SERVICES.md +1252 -0
  52. crackerjack/documentation/__init__.py +31 -0
  53. crackerjack/documentation/ai_templates.py +756 -0
  54. crackerjack/documentation/dual_output_generator.py +765 -0
  55. crackerjack/documentation/mkdocs_integration.py +518 -0
  56. crackerjack/documentation/reference_generator.py +977 -0
  57. crackerjack/dynamic_config.py +55 -50
  58. crackerjack/executors/async_hook_executor.py +10 -15
  59. crackerjack/executors/cached_hook_executor.py +117 -43
  60. crackerjack/executors/hook_executor.py +8 -34
  61. crackerjack/executors/hook_lock_manager.py +26 -183
  62. crackerjack/executors/individual_hook_executor.py +13 -11
  63. crackerjack/executors/lsp_aware_hook_executor.py +270 -0
  64. crackerjack/executors/tool_proxy.py +417 -0
  65. crackerjack/hooks/lsp_hook.py +79 -0
  66. crackerjack/intelligence/adaptive_learning.py +25 -10
  67. crackerjack/intelligence/agent_orchestrator.py +2 -5
  68. crackerjack/intelligence/agent_registry.py +34 -24
  69. crackerjack/intelligence/agent_selector.py +5 -7
  70. crackerjack/interactive.py +17 -6
  71. crackerjack/managers/async_hook_manager.py +0 -1
  72. crackerjack/managers/hook_manager.py +79 -1
  73. crackerjack/managers/publish_manager.py +66 -13
  74. crackerjack/managers/test_command_builder.py +5 -17
  75. crackerjack/managers/test_executor.py +1 -3
  76. crackerjack/managers/test_manager.py +109 -7
  77. crackerjack/managers/test_manager_backup.py +10 -9
  78. crackerjack/mcp/cache.py +2 -2
  79. crackerjack/mcp/client_runner.py +1 -1
  80. crackerjack/mcp/context.py +191 -68
  81. crackerjack/mcp/dashboard.py +7 -5
  82. crackerjack/mcp/enhanced_progress_monitor.py +31 -28
  83. crackerjack/mcp/file_monitor.py +30 -23
  84. crackerjack/mcp/progress_components.py +31 -21
  85. crackerjack/mcp/progress_monitor.py +50 -53
  86. crackerjack/mcp/rate_limiter.py +6 -6
  87. crackerjack/mcp/server_core.py +161 -32
  88. crackerjack/mcp/service_watchdog.py +2 -1
  89. crackerjack/mcp/state.py +4 -7
  90. crackerjack/mcp/task_manager.py +11 -9
  91. crackerjack/mcp/tools/core_tools.py +174 -33
  92. crackerjack/mcp/tools/error_analyzer.py +3 -2
  93. crackerjack/mcp/tools/execution_tools.py +15 -12
  94. crackerjack/mcp/tools/execution_tools_backup.py +42 -30
  95. crackerjack/mcp/tools/intelligence_tool_registry.py +7 -5
  96. crackerjack/mcp/tools/intelligence_tools.py +5 -2
  97. crackerjack/mcp/tools/monitoring_tools.py +33 -70
  98. crackerjack/mcp/tools/proactive_tools.py +24 -11
  99. crackerjack/mcp/tools/progress_tools.py +5 -8
  100. crackerjack/mcp/tools/utility_tools.py +20 -14
  101. crackerjack/mcp/tools/workflow_executor.py +62 -40
  102. crackerjack/mcp/websocket/app.py +8 -0
  103. crackerjack/mcp/websocket/endpoints.py +352 -357
  104. crackerjack/mcp/websocket/jobs.py +40 -57
  105. crackerjack/mcp/websocket/monitoring_endpoints.py +2935 -0
  106. crackerjack/mcp/websocket/server.py +7 -25
  107. crackerjack/mcp/websocket/websocket_handler.py +6 -17
  108. crackerjack/mixins/__init__.py +3 -0
  109. crackerjack/mixins/error_handling.py +145 -0
  110. crackerjack/models/config.py +21 -1
  111. crackerjack/models/config_adapter.py +49 -1
  112. crackerjack/models/protocols.py +176 -107
  113. crackerjack/models/resource_protocols.py +55 -210
  114. crackerjack/models/task.py +3 -0
  115. crackerjack/monitoring/ai_agent_watchdog.py +13 -13
  116. crackerjack/monitoring/metrics_collector.py +426 -0
  117. crackerjack/monitoring/regression_prevention.py +8 -8
  118. crackerjack/monitoring/websocket_server.py +643 -0
  119. crackerjack/orchestration/advanced_orchestrator.py +11 -6
  120. crackerjack/orchestration/coverage_improvement.py +3 -3
  121. crackerjack/orchestration/execution_strategies.py +26 -6
  122. crackerjack/orchestration/test_progress_streamer.py +8 -5
  123. crackerjack/plugins/base.py +2 -2
  124. crackerjack/plugins/hooks.py +7 -0
  125. crackerjack/plugins/managers.py +11 -8
  126. crackerjack/security/__init__.py +0 -1
  127. crackerjack/security/audit.py +90 -105
  128. crackerjack/services/anomaly_detector.py +392 -0
  129. crackerjack/services/api_extractor.py +615 -0
  130. crackerjack/services/backup_service.py +2 -2
  131. crackerjack/services/bounded_status_operations.py +15 -152
  132. crackerjack/services/cache.py +127 -1
  133. crackerjack/services/changelog_automation.py +395 -0
  134. crackerjack/services/config.py +18 -11
  135. crackerjack/services/config_merge.py +30 -85
  136. crackerjack/services/config_template.py +506 -0
  137. crackerjack/services/contextual_ai_assistant.py +48 -22
  138. crackerjack/services/coverage_badge_service.py +171 -0
  139. crackerjack/services/coverage_ratchet.py +41 -17
  140. crackerjack/services/debug.py +3 -3
  141. crackerjack/services/dependency_analyzer.py +460 -0
  142. crackerjack/services/dependency_monitor.py +14 -11
  143. crackerjack/services/documentation_generator.py +491 -0
  144. crackerjack/services/documentation_service.py +675 -0
  145. crackerjack/services/enhanced_filesystem.py +6 -5
  146. crackerjack/services/enterprise_optimizer.py +865 -0
  147. crackerjack/services/error_pattern_analyzer.py +676 -0
  148. crackerjack/services/file_hasher.py +1 -1
  149. crackerjack/services/git.py +41 -45
  150. crackerjack/services/health_metrics.py +10 -8
  151. crackerjack/services/heatmap_generator.py +735 -0
  152. crackerjack/services/initialization.py +30 -33
  153. crackerjack/services/input_validator.py +5 -97
  154. crackerjack/services/intelligent_commit.py +327 -0
  155. crackerjack/services/log_manager.py +15 -12
  156. crackerjack/services/logging.py +4 -3
  157. crackerjack/services/lsp_client.py +628 -0
  158. crackerjack/services/memory_optimizer.py +409 -0
  159. crackerjack/services/metrics.py +42 -33
  160. crackerjack/services/parallel_executor.py +416 -0
  161. crackerjack/services/pattern_cache.py +1 -1
  162. crackerjack/services/pattern_detector.py +6 -6
  163. crackerjack/services/performance_benchmarks.py +250 -576
  164. crackerjack/services/performance_cache.py +382 -0
  165. crackerjack/services/performance_monitor.py +565 -0
  166. crackerjack/services/predictive_analytics.py +510 -0
  167. crackerjack/services/quality_baseline.py +234 -0
  168. crackerjack/services/quality_baseline_enhanced.py +646 -0
  169. crackerjack/services/quality_intelligence.py +785 -0
  170. crackerjack/services/regex_patterns.py +605 -524
  171. crackerjack/services/regex_utils.py +43 -123
  172. crackerjack/services/secure_path_utils.py +5 -164
  173. crackerjack/services/secure_status_formatter.py +30 -141
  174. crackerjack/services/secure_subprocess.py +11 -92
  175. crackerjack/services/security.py +61 -30
  176. crackerjack/services/security_logger.py +18 -22
  177. crackerjack/services/server_manager.py +124 -16
  178. crackerjack/services/status_authentication.py +16 -159
  179. crackerjack/services/status_security_manager.py +4 -131
  180. crackerjack/services/terminal_utils.py +0 -0
  181. crackerjack/services/thread_safe_status_collector.py +19 -125
  182. crackerjack/services/unified_config.py +21 -13
  183. crackerjack/services/validation_rate_limiter.py +5 -54
  184. crackerjack/services/version_analyzer.py +459 -0
  185. crackerjack/services/version_checker.py +1 -1
  186. crackerjack/services/websocket_resource_limiter.py +10 -144
  187. crackerjack/services/zuban_lsp_service.py +390 -0
  188. crackerjack/slash_commands/__init__.py +2 -7
  189. crackerjack/slash_commands/run.md +2 -2
  190. crackerjack/tools/validate_input_validator_patterns.py +14 -40
  191. crackerjack/tools/validate_regex_patterns.py +19 -48
  192. {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.dist-info}/METADATA +197 -26
  193. crackerjack-0.33.1.dist-info/RECORD +229 -0
  194. crackerjack/CLAUDE.md +0 -207
  195. crackerjack/RULES.md +0 -380
  196. crackerjack/py313.py +0 -234
  197. crackerjack-0.32.0.dist-info/RECORD +0 -180
  198. {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.dist-info}/WHEEL +0 -0
  199. {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.dist-info}/entry_points.txt +0 -0
  200. {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.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
- """States for circuit breaker pattern."""
21
-
22
- CLOSED = "closed" # Normal operation
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
- self._client_operations: dict[str, deque[float]] = defaultdict(
108
- lambda: deque(maxlen=100)
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:.1f}MB",
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:.1f}MB"
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:.2f}s",
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:.2f}s",
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) # Monitor every 100ms
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
@@ -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
- self.cache_dir = cache_dir or Path.cwd() / ".crackerjack_cache"
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}"