crackerjack 0.32.0__py3-none-any.whl → 0.33.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of crackerjack might be problematic. Click here for more details.
- crackerjack/__main__.py +1350 -34
- crackerjack/adapters/__init__.py +17 -0
- crackerjack/adapters/lsp_client.py +358 -0
- crackerjack/adapters/rust_tool_adapter.py +194 -0
- crackerjack/adapters/rust_tool_manager.py +193 -0
- crackerjack/adapters/skylos_adapter.py +231 -0
- crackerjack/adapters/zuban_adapter.py +560 -0
- crackerjack/agents/base.py +7 -3
- crackerjack/agents/coordinator.py +271 -33
- crackerjack/agents/documentation_agent.py +9 -15
- crackerjack/agents/dry_agent.py +3 -15
- crackerjack/agents/formatting_agent.py +1 -1
- crackerjack/agents/import_optimization_agent.py +36 -180
- crackerjack/agents/performance_agent.py +17 -98
- crackerjack/agents/performance_helpers.py +7 -31
- crackerjack/agents/proactive_agent.py +1 -3
- crackerjack/agents/refactoring_agent.py +16 -85
- crackerjack/agents/refactoring_helpers.py +7 -42
- crackerjack/agents/security_agent.py +9 -48
- crackerjack/agents/test_creation_agent.py +356 -513
- crackerjack/agents/test_specialist_agent.py +0 -4
- crackerjack/api.py +6 -25
- crackerjack/cli/cache_handlers.py +204 -0
- crackerjack/cli/cache_handlers_enhanced.py +683 -0
- crackerjack/cli/facade.py +100 -0
- crackerjack/cli/handlers.py +224 -9
- crackerjack/cli/interactive.py +6 -4
- crackerjack/cli/options.py +642 -55
- crackerjack/cli/utils.py +2 -1
- crackerjack/code_cleaner.py +58 -117
- crackerjack/config/global_lock_config.py +8 -48
- crackerjack/config/hooks.py +53 -62
- crackerjack/core/async_workflow_orchestrator.py +24 -34
- crackerjack/core/autofix_coordinator.py +3 -17
- crackerjack/core/enhanced_container.py +64 -6
- crackerjack/core/file_lifecycle.py +12 -89
- crackerjack/core/performance.py +2 -2
- crackerjack/core/performance_monitor.py +15 -55
- crackerjack/core/phase_coordinator.py +257 -218
- crackerjack/core/resource_manager.py +14 -90
- crackerjack/core/service_watchdog.py +62 -95
- crackerjack/core/session_coordinator.py +149 -0
- crackerjack/core/timeout_manager.py +14 -72
- crackerjack/core/websocket_lifecycle.py +13 -78
- crackerjack/core/workflow_orchestrator.py +558 -240
- crackerjack/docs/INDEX.md +11 -0
- crackerjack/docs/generated/api/API_REFERENCE.md +10895 -0
- crackerjack/docs/generated/api/CLI_REFERENCE.md +109 -0
- crackerjack/docs/generated/api/CROSS_REFERENCES.md +1755 -0
- crackerjack/docs/generated/api/PROTOCOLS.md +3 -0
- crackerjack/docs/generated/api/SERVICES.md +1252 -0
- crackerjack/documentation/__init__.py +31 -0
- crackerjack/documentation/ai_templates.py +756 -0
- crackerjack/documentation/dual_output_generator.py +765 -0
- crackerjack/documentation/mkdocs_integration.py +518 -0
- crackerjack/documentation/reference_generator.py +977 -0
- crackerjack/dynamic_config.py +55 -50
- crackerjack/executors/async_hook_executor.py +10 -15
- crackerjack/executors/cached_hook_executor.py +117 -43
- crackerjack/executors/hook_executor.py +8 -34
- crackerjack/executors/hook_lock_manager.py +26 -183
- crackerjack/executors/individual_hook_executor.py +13 -11
- crackerjack/executors/lsp_aware_hook_executor.py +270 -0
- crackerjack/executors/tool_proxy.py +417 -0
- crackerjack/hooks/lsp_hook.py +79 -0
- crackerjack/intelligence/adaptive_learning.py +25 -10
- crackerjack/intelligence/agent_orchestrator.py +2 -5
- crackerjack/intelligence/agent_registry.py +34 -24
- crackerjack/intelligence/agent_selector.py +5 -7
- crackerjack/interactive.py +17 -6
- crackerjack/managers/async_hook_manager.py +0 -1
- crackerjack/managers/hook_manager.py +79 -1
- crackerjack/managers/publish_manager.py +66 -13
- crackerjack/managers/test_command_builder.py +5 -17
- crackerjack/managers/test_executor.py +1 -3
- crackerjack/managers/test_manager.py +109 -7
- crackerjack/managers/test_manager_backup.py +10 -9
- crackerjack/mcp/cache.py +2 -2
- crackerjack/mcp/client_runner.py +1 -1
- crackerjack/mcp/context.py +191 -68
- crackerjack/mcp/dashboard.py +7 -5
- crackerjack/mcp/enhanced_progress_monitor.py +31 -28
- crackerjack/mcp/file_monitor.py +30 -23
- crackerjack/mcp/progress_components.py +31 -21
- crackerjack/mcp/progress_monitor.py +50 -53
- crackerjack/mcp/rate_limiter.py +6 -6
- crackerjack/mcp/server_core.py +161 -32
- crackerjack/mcp/service_watchdog.py +2 -1
- crackerjack/mcp/state.py +4 -7
- crackerjack/mcp/task_manager.py +11 -9
- crackerjack/mcp/tools/core_tools.py +174 -33
- crackerjack/mcp/tools/error_analyzer.py +3 -2
- crackerjack/mcp/tools/execution_tools.py +15 -12
- crackerjack/mcp/tools/execution_tools_backup.py +42 -30
- crackerjack/mcp/tools/intelligence_tool_registry.py +7 -5
- crackerjack/mcp/tools/intelligence_tools.py +5 -2
- crackerjack/mcp/tools/monitoring_tools.py +33 -70
- crackerjack/mcp/tools/proactive_tools.py +24 -11
- crackerjack/mcp/tools/progress_tools.py +5 -8
- crackerjack/mcp/tools/utility_tools.py +20 -14
- crackerjack/mcp/tools/workflow_executor.py +62 -40
- crackerjack/mcp/websocket/app.py +8 -0
- crackerjack/mcp/websocket/endpoints.py +352 -357
- crackerjack/mcp/websocket/jobs.py +40 -57
- crackerjack/mcp/websocket/monitoring_endpoints.py +2935 -0
- crackerjack/mcp/websocket/server.py +7 -25
- crackerjack/mcp/websocket/websocket_handler.py +6 -17
- crackerjack/mixins/__init__.py +3 -0
- crackerjack/mixins/error_handling.py +145 -0
- crackerjack/models/config.py +21 -1
- crackerjack/models/config_adapter.py +49 -1
- crackerjack/models/protocols.py +176 -107
- crackerjack/models/resource_protocols.py +55 -210
- crackerjack/models/task.py +3 -0
- crackerjack/monitoring/ai_agent_watchdog.py +13 -13
- crackerjack/monitoring/metrics_collector.py +426 -0
- crackerjack/monitoring/regression_prevention.py +8 -8
- crackerjack/monitoring/websocket_server.py +643 -0
- crackerjack/orchestration/advanced_orchestrator.py +11 -6
- crackerjack/orchestration/coverage_improvement.py +3 -3
- crackerjack/orchestration/execution_strategies.py +26 -6
- crackerjack/orchestration/test_progress_streamer.py +8 -5
- crackerjack/plugins/base.py +2 -2
- crackerjack/plugins/hooks.py +7 -0
- crackerjack/plugins/managers.py +11 -8
- crackerjack/security/__init__.py +0 -1
- crackerjack/security/audit.py +90 -105
- crackerjack/services/anomaly_detector.py +392 -0
- crackerjack/services/api_extractor.py +615 -0
- crackerjack/services/backup_service.py +2 -2
- crackerjack/services/bounded_status_operations.py +15 -152
- crackerjack/services/cache.py +127 -1
- crackerjack/services/changelog_automation.py +395 -0
- crackerjack/services/config.py +18 -11
- crackerjack/services/config_merge.py +30 -85
- crackerjack/services/config_template.py +506 -0
- crackerjack/services/contextual_ai_assistant.py +48 -22
- crackerjack/services/coverage_badge_service.py +171 -0
- crackerjack/services/coverage_ratchet.py +41 -17
- crackerjack/services/debug.py +3 -3
- crackerjack/services/dependency_analyzer.py +460 -0
- crackerjack/services/dependency_monitor.py +14 -11
- crackerjack/services/documentation_generator.py +491 -0
- crackerjack/services/documentation_service.py +675 -0
- crackerjack/services/enhanced_filesystem.py +6 -5
- crackerjack/services/enterprise_optimizer.py +865 -0
- crackerjack/services/error_pattern_analyzer.py +676 -0
- crackerjack/services/file_hasher.py +1 -1
- crackerjack/services/git.py +41 -45
- crackerjack/services/health_metrics.py +10 -8
- crackerjack/services/heatmap_generator.py +735 -0
- crackerjack/services/initialization.py +30 -33
- crackerjack/services/input_validator.py +5 -97
- crackerjack/services/intelligent_commit.py +327 -0
- crackerjack/services/log_manager.py +15 -12
- crackerjack/services/logging.py +4 -3
- crackerjack/services/lsp_client.py +628 -0
- crackerjack/services/memory_optimizer.py +409 -0
- crackerjack/services/metrics.py +42 -33
- crackerjack/services/parallel_executor.py +416 -0
- crackerjack/services/pattern_cache.py +1 -1
- crackerjack/services/pattern_detector.py +6 -6
- crackerjack/services/performance_benchmarks.py +250 -576
- crackerjack/services/performance_cache.py +382 -0
- crackerjack/services/performance_monitor.py +565 -0
- crackerjack/services/predictive_analytics.py +510 -0
- crackerjack/services/quality_baseline.py +234 -0
- crackerjack/services/quality_baseline_enhanced.py +646 -0
- crackerjack/services/quality_intelligence.py +785 -0
- crackerjack/services/regex_patterns.py +605 -524
- crackerjack/services/regex_utils.py +43 -123
- crackerjack/services/secure_path_utils.py +5 -164
- crackerjack/services/secure_status_formatter.py +30 -141
- crackerjack/services/secure_subprocess.py +11 -92
- crackerjack/services/security.py +61 -30
- crackerjack/services/security_logger.py +18 -22
- crackerjack/services/server_manager.py +124 -16
- crackerjack/services/status_authentication.py +16 -159
- crackerjack/services/status_security_manager.py +4 -131
- crackerjack/services/terminal_utils.py +0 -0
- crackerjack/services/thread_safe_status_collector.py +19 -125
- crackerjack/services/unified_config.py +21 -13
- crackerjack/services/validation_rate_limiter.py +5 -54
- crackerjack/services/version_analyzer.py +459 -0
- crackerjack/services/version_checker.py +1 -1
- crackerjack/services/websocket_resource_limiter.py +10 -144
- crackerjack/services/zuban_lsp_service.py +390 -0
- crackerjack/slash_commands/__init__.py +2 -7
- crackerjack/slash_commands/run.md +2 -2
- crackerjack/tools/validate_input_validator_patterns.py +14 -40
- crackerjack/tools/validate_regex_patterns.py +19 -48
- {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.dist-info}/METADATA +197 -26
- crackerjack-0.33.1.dist-info/RECORD +229 -0
- crackerjack/CLAUDE.md +0 -207
- crackerjack/RULES.md +0 -380
- crackerjack/py313.py +0 -234
- crackerjack-0.32.0.dist-info/RECORD +0 -180
- {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.dist-info}/WHEEL +0 -0
- {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.dist-info}/entry_points.txt +0 -0
- {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import builtins
|
|
3
|
+
import hashlib
|
|
4
|
+
import typing as t
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from datetime import datetime, timedelta
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from threading import Lock
|
|
9
|
+
from weakref import WeakValueDictionary
|
|
10
|
+
|
|
11
|
+
from crackerjack.services.logging import get_logger
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class CacheEntry:
|
|
16
|
+
value: t.Any
|
|
17
|
+
created_at: datetime
|
|
18
|
+
access_count: int = 0
|
|
19
|
+
last_accessed: datetime = field(default_factory=datetime.now)
|
|
20
|
+
ttl_seconds: int = 300
|
|
21
|
+
invalidation_keys: set[str] = field(default_factory=set)
|
|
22
|
+
|
|
23
|
+
def is_expired(self) -> bool:
|
|
24
|
+
if self.ttl_seconds <= 0:
|
|
25
|
+
return False
|
|
26
|
+
return datetime.now() > self.created_at + timedelta(seconds=self.ttl_seconds)
|
|
27
|
+
|
|
28
|
+
def access(self) -> t.Any:
|
|
29
|
+
self.access_count += 1
|
|
30
|
+
self.last_accessed = datetime.now()
|
|
31
|
+
return self.value
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class CacheStats:
|
|
36
|
+
hits: int = 0
|
|
37
|
+
misses: int = 0
|
|
38
|
+
evictions: int = 0
|
|
39
|
+
memory_usage_bytes: int = 0
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def hit_ratio(self) -> float:
|
|
43
|
+
total = self.hits + self.misses
|
|
44
|
+
return self.hits / total if total > 0 else 0.0
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class PerformanceCache:
|
|
48
|
+
def __init__(
|
|
49
|
+
self,
|
|
50
|
+
max_memory_mb: int = 50,
|
|
51
|
+
default_ttl_seconds: int = 300,
|
|
52
|
+
cleanup_interval_seconds: int = 60,
|
|
53
|
+
):
|
|
54
|
+
self.max_memory_bytes = max_memory_mb * 1024 * 1024
|
|
55
|
+
self.default_ttl_seconds = default_ttl_seconds
|
|
56
|
+
self.cleanup_interval_seconds = cleanup_interval_seconds
|
|
57
|
+
|
|
58
|
+
self._cache: dict[str, CacheEntry] = {}
|
|
59
|
+
self._lock = Lock()
|
|
60
|
+
self._stats = CacheStats()
|
|
61
|
+
self._logger = get_logger("crackerjack.performance_cache")
|
|
62
|
+
self._cleanup_task: asyncio.Task[None] | None = None
|
|
63
|
+
self._invalidation_map: dict[str, set[str]] = {}
|
|
64
|
+
|
|
65
|
+
self._weak_cache: WeakValueDictionary[str, t.Any] = WeakValueDictionary()
|
|
66
|
+
|
|
67
|
+
async def start(self) -> None:
|
|
68
|
+
if self._cleanup_task is None:
|
|
69
|
+
self._cleanup_task = asyncio.create_task(self._cleanup_loop())
|
|
70
|
+
self._logger.info("Performance cache started")
|
|
71
|
+
|
|
72
|
+
async def stop(self) -> None:
|
|
73
|
+
if self._cleanup_task:
|
|
74
|
+
self._cleanup_task.cancel()
|
|
75
|
+
try:
|
|
76
|
+
await self._cleanup_task
|
|
77
|
+
except asyncio.CancelledError:
|
|
78
|
+
pass
|
|
79
|
+
self._cleanup_task = None
|
|
80
|
+
self._logger.info("Performance cache stopped")
|
|
81
|
+
|
|
82
|
+
def get(
|
|
83
|
+
self,
|
|
84
|
+
key: str,
|
|
85
|
+
default: t.Any = None,
|
|
86
|
+
) -> t.Any:
|
|
87
|
+
with self._lock:
|
|
88
|
+
if key not in self._cache:
|
|
89
|
+
self._stats.misses += 1
|
|
90
|
+
return default
|
|
91
|
+
|
|
92
|
+
entry = self._cache[key]
|
|
93
|
+
if entry.is_expired():
|
|
94
|
+
del self._cache[key]
|
|
95
|
+
self._stats.misses += 1
|
|
96
|
+
self._stats.evictions += 1
|
|
97
|
+
return default
|
|
98
|
+
|
|
99
|
+
self._stats.hits += 1
|
|
100
|
+
return entry.access()
|
|
101
|
+
|
|
102
|
+
async def get_async(
|
|
103
|
+
self,
|
|
104
|
+
key: str,
|
|
105
|
+
default: t.Any = None,
|
|
106
|
+
) -> t.Any:
|
|
107
|
+
return self.get(key, default)
|
|
108
|
+
|
|
109
|
+
def set(
|
|
110
|
+
self,
|
|
111
|
+
key: str,
|
|
112
|
+
value: t.Any,
|
|
113
|
+
ttl_seconds: int | None = None,
|
|
114
|
+
invalidation_keys: set[str] | None = None,
|
|
115
|
+
) -> None:
|
|
116
|
+
ttl = ttl_seconds if ttl_seconds is not None else self.default_ttl_seconds
|
|
117
|
+
inv_keys = invalidation_keys or set()
|
|
118
|
+
|
|
119
|
+
entry = CacheEntry(
|
|
120
|
+
value=value,
|
|
121
|
+
created_at=datetime.now(),
|
|
122
|
+
ttl_seconds=ttl,
|
|
123
|
+
invalidation_keys=inv_keys,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
with self._lock:
|
|
127
|
+
self._cache[key] = entry
|
|
128
|
+
|
|
129
|
+
for inv_key in inv_keys:
|
|
130
|
+
if inv_key not in self._invalidation_map:
|
|
131
|
+
self._invalidation_map[inv_key] = set()
|
|
132
|
+
self._invalidation_map[inv_key].add(key)
|
|
133
|
+
|
|
134
|
+
self._check_memory_pressure()
|
|
135
|
+
|
|
136
|
+
async def set_async(
|
|
137
|
+
self,
|
|
138
|
+
key: str,
|
|
139
|
+
value: t.Any,
|
|
140
|
+
ttl_seconds: int | None = None,
|
|
141
|
+
invalidation_keys: builtins.set[str] | None = None,
|
|
142
|
+
) -> None:
|
|
143
|
+
self.set[t.Any](key, value, ttl_seconds, invalidation_keys)
|
|
144
|
+
|
|
145
|
+
def invalidate(self, invalidation_key: str) -> int:
|
|
146
|
+
with self._lock:
|
|
147
|
+
if invalidation_key not in self._invalidation_map:
|
|
148
|
+
return 0
|
|
149
|
+
|
|
150
|
+
keys_to_remove = self._invalidation_map[invalidation_key].copy()
|
|
151
|
+
count = 0
|
|
152
|
+
|
|
153
|
+
for key in keys_to_remove:
|
|
154
|
+
if key in self._cache:
|
|
155
|
+
del self._cache[key]
|
|
156
|
+
count += 1
|
|
157
|
+
self._stats.evictions += 1
|
|
158
|
+
|
|
159
|
+
del self._invalidation_map[invalidation_key]
|
|
160
|
+
|
|
161
|
+
self._logger.debug(
|
|
162
|
+
f"Invalidated {count} cache entries for key: {invalidation_key}"
|
|
163
|
+
)
|
|
164
|
+
return count
|
|
165
|
+
|
|
166
|
+
def clear(self) -> None:
|
|
167
|
+
with self._lock:
|
|
168
|
+
count = len(self._cache)
|
|
169
|
+
self._cache.clear()
|
|
170
|
+
self._invalidation_map.clear()
|
|
171
|
+
self._stats.evictions += count
|
|
172
|
+
self._logger.info(f"Cleared {count} cache entries")
|
|
173
|
+
|
|
174
|
+
def get_stats(self) -> CacheStats:
|
|
175
|
+
with self._lock:
|
|
176
|
+
stats = CacheStats(
|
|
177
|
+
hits=self._stats.hits,
|
|
178
|
+
misses=self._stats.misses,
|
|
179
|
+
evictions=self._stats.evictions,
|
|
180
|
+
memory_usage_bytes=self._estimate_memory_usage(),
|
|
181
|
+
)
|
|
182
|
+
return stats
|
|
183
|
+
|
|
184
|
+
def _estimate_memory_usage(self) -> int:
|
|
185
|
+
total_size = 0
|
|
186
|
+
for entry in self._cache.values():
|
|
187
|
+
if isinstance(entry.value, str | bytes):
|
|
188
|
+
total_size += len(entry.value)
|
|
189
|
+
elif isinstance(entry.value, list | tuple):
|
|
190
|
+
total_size += len(entry.value) * 100
|
|
191
|
+
elif isinstance(entry.value, dict):
|
|
192
|
+
total_size += len(entry.value) * 200
|
|
193
|
+
else:
|
|
194
|
+
total_size += 1000
|
|
195
|
+
|
|
196
|
+
return total_size
|
|
197
|
+
|
|
198
|
+
def _check_memory_pressure(self) -> None:
|
|
199
|
+
if self._estimate_memory_usage() > self.max_memory_bytes:
|
|
200
|
+
self._evict_lru_entries()
|
|
201
|
+
|
|
202
|
+
def _evict_lru_entries(self) -> None:
|
|
203
|
+
if not self._cache:
|
|
204
|
+
return
|
|
205
|
+
|
|
206
|
+
entries_by_access = sorted(
|
|
207
|
+
self._cache.items(),
|
|
208
|
+
key=lambda x: x[1].last_accessed,
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
evict_count = max(1, len(entries_by_access) // 4)
|
|
212
|
+
|
|
213
|
+
for key, _ in entries_by_access[:evict_count]:
|
|
214
|
+
del self._cache[key]
|
|
215
|
+
self._stats.evictions += 1
|
|
216
|
+
|
|
217
|
+
self._logger.debug(f"Evicted {evict_count} LRU cache entries")
|
|
218
|
+
|
|
219
|
+
async def _cleanup_loop(self) -> None:
|
|
220
|
+
while True:
|
|
221
|
+
try:
|
|
222
|
+
await asyncio.sleep(self.cleanup_interval_seconds)
|
|
223
|
+
self._cleanup_expired_entries()
|
|
224
|
+
except asyncio.CancelledError:
|
|
225
|
+
break
|
|
226
|
+
except Exception as e:
|
|
227
|
+
self._logger.error(f"Error in cache cleanup loop: {e}")
|
|
228
|
+
|
|
229
|
+
def _cleanup_expired_entries(self) -> None:
|
|
230
|
+
with self._lock:
|
|
231
|
+
expired_keys = [
|
|
232
|
+
key for key, entry in self._cache.items() if entry.is_expired()
|
|
233
|
+
]
|
|
234
|
+
|
|
235
|
+
for key in expired_keys:
|
|
236
|
+
del self._cache[key]
|
|
237
|
+
self._stats.evictions += 1
|
|
238
|
+
|
|
239
|
+
if expired_keys:
|
|
240
|
+
self._logger.debug(f"Cleaned up {len(expired_keys)} expired entries")
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
class GitOperationCache:
|
|
244
|
+
def __init__(self, cache: PerformanceCache):
|
|
245
|
+
self.cache = cache
|
|
246
|
+
self._logger = get_logger("crackerjack.git_cache")
|
|
247
|
+
|
|
248
|
+
def _make_repo_key(self, repo_path: Path, operation: str, params: str = "") -> str:
|
|
249
|
+
repo_hash = hashlib.md5(
|
|
250
|
+
str(repo_path).encode(), usedforsecurity=False
|
|
251
|
+
).hexdigest()[:8]
|
|
252
|
+
param_hash = (
|
|
253
|
+
hashlib.md5(params.encode(), usedforsecurity=False).hexdigest()[:8]
|
|
254
|
+
if params
|
|
255
|
+
else ""
|
|
256
|
+
)
|
|
257
|
+
return f"git: {repo_hash}: {operation}: {param_hash}"
|
|
258
|
+
|
|
259
|
+
def get_branch_info(self, repo_path: Path) -> t.Any:
|
|
260
|
+
key = self._make_repo_key(repo_path, "branch_info")
|
|
261
|
+
return self.cache.get(key)
|
|
262
|
+
|
|
263
|
+
def set_branch_info(
|
|
264
|
+
self,
|
|
265
|
+
repo_path: Path,
|
|
266
|
+
branch_info: t.Any,
|
|
267
|
+
ttl_seconds: int = 60,
|
|
268
|
+
) -> None:
|
|
269
|
+
key = self._make_repo_key(repo_path, "branch_info")
|
|
270
|
+
invalidation_keys = {f"git_repo: {repo_path}"}
|
|
271
|
+
self.cache.set[t.Any](key, branch_info, ttl_seconds, invalidation_keys)
|
|
272
|
+
|
|
273
|
+
def get_file_status(self, repo_path: Path) -> t.Any:
|
|
274
|
+
key = self._make_repo_key(repo_path, "file_status")
|
|
275
|
+
return self.cache.get(key)
|
|
276
|
+
|
|
277
|
+
def set_file_status(
|
|
278
|
+
self,
|
|
279
|
+
repo_path: Path,
|
|
280
|
+
file_status: t.Any,
|
|
281
|
+
ttl_seconds: int = 30,
|
|
282
|
+
) -> None:
|
|
283
|
+
key = self._make_repo_key(repo_path, "file_status")
|
|
284
|
+
invalidation_keys = {f"git_repo: {repo_path}", "git_files"}
|
|
285
|
+
self.cache.set[t.Any](key, file_status, ttl_seconds, invalidation_keys)
|
|
286
|
+
|
|
287
|
+
def invalidate_repo(self, repo_path: Path) -> None:
|
|
288
|
+
self.cache.invalidate(f"git_repo: {repo_path}")
|
|
289
|
+
self._logger.debug(f"Invalidated git cache for repo: {repo_path}")
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
class FileSystemCache:
|
|
293
|
+
def __init__(self, cache: PerformanceCache):
|
|
294
|
+
self.cache = cache
|
|
295
|
+
self._logger = get_logger("crackerjack.filesystem_cache")
|
|
296
|
+
|
|
297
|
+
def _make_file_key(self, file_path: Path, operation: str) -> str:
|
|
298
|
+
file_hash = hashlib.md5(
|
|
299
|
+
str(file_path).encode(), usedforsecurity=False
|
|
300
|
+
).hexdigest()[:8]
|
|
301
|
+
return f"fs: {file_hash}: {operation}"
|
|
302
|
+
|
|
303
|
+
def get_file_stats(self, file_path: Path) -> t.Any:
|
|
304
|
+
key = self._make_file_key(file_path, "stats")
|
|
305
|
+
return self.cache.get(key)
|
|
306
|
+
|
|
307
|
+
def set_file_stats(
|
|
308
|
+
self,
|
|
309
|
+
file_path: Path,
|
|
310
|
+
stats: t.Any,
|
|
311
|
+
ttl_seconds: int = 60,
|
|
312
|
+
) -> None:
|
|
313
|
+
key = self._make_file_key(file_path, "stats")
|
|
314
|
+
invalidation_keys = {f"file: {file_path}"}
|
|
315
|
+
self.cache.set[t.Any](key, stats, ttl_seconds, invalidation_keys)
|
|
316
|
+
|
|
317
|
+
def invalidate_file(self, file_path: Path) -> None:
|
|
318
|
+
self.cache.invalidate(f"file: {file_path}")
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
class CommandResultCache:
|
|
322
|
+
def __init__(self, cache: PerformanceCache):
|
|
323
|
+
self.cache = cache
|
|
324
|
+
self._logger = get_logger("crackerjack.command_cache")
|
|
325
|
+
|
|
326
|
+
def _make_command_key(self, command: list[str], cwd: Path | None = None) -> str:
|
|
327
|
+
cmd_str = " ".join(command)
|
|
328
|
+
cwd_str = str(cwd) if cwd else ""
|
|
329
|
+
combined = f"{cmd_str}: {cwd_str}"
|
|
330
|
+
cmd_hash = hashlib.md5(combined.encode(), usedforsecurity=False).hexdigest()[
|
|
331
|
+
:12
|
|
332
|
+
]
|
|
333
|
+
return f"cmd: {cmd_hash}"
|
|
334
|
+
|
|
335
|
+
def get_command_result(
|
|
336
|
+
self,
|
|
337
|
+
command: list[str],
|
|
338
|
+
cwd: Path | None = None,
|
|
339
|
+
) -> t.Any:
|
|
340
|
+
key = self._make_command_key(command, cwd)
|
|
341
|
+
return self.cache.get(key)
|
|
342
|
+
|
|
343
|
+
def set_command_result(
|
|
344
|
+
self,
|
|
345
|
+
command: list[str],
|
|
346
|
+
result: t.Any,
|
|
347
|
+
cwd: Path | None = None,
|
|
348
|
+
ttl_seconds: int = 120,
|
|
349
|
+
) -> None:
|
|
350
|
+
key = self._make_command_key(command, cwd)
|
|
351
|
+
invalidation_keys = {"commands"}
|
|
352
|
+
if cwd:
|
|
353
|
+
invalidation_keys.add(f"cwd: {cwd}")
|
|
354
|
+
|
|
355
|
+
self.cache.set[t.Any](key, result, ttl_seconds, invalidation_keys)
|
|
356
|
+
|
|
357
|
+
def invalidate_commands(self) -> None:
|
|
358
|
+
self.cache.invalidate("commands")
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
_global_cache: PerformanceCache | None = None
|
|
362
|
+
_cache_lock = Lock()
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def get_performance_cache() -> PerformanceCache:
|
|
366
|
+
global _global_cache
|
|
367
|
+
with _cache_lock:
|
|
368
|
+
if _global_cache is None:
|
|
369
|
+
_global_cache = PerformanceCache()
|
|
370
|
+
return _global_cache
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def get_git_cache() -> GitOperationCache:
|
|
374
|
+
return GitOperationCache(get_performance_cache())
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def get_filesystem_cache() -> FileSystemCache:
|
|
378
|
+
return FileSystemCache(get_performance_cache())
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def get_command_cache() -> CommandResultCache:
|
|
382
|
+
return CommandResultCache(get_performance_cache())
|