crackerjack 0.31.10__py3-none-any.whl → 0.31.12__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/CLAUDE.md +288 -705
- crackerjack/__main__.py +22 -8
- crackerjack/agents/__init__.py +0 -3
- crackerjack/agents/architect_agent.py +0 -43
- crackerjack/agents/base.py +1 -9
- crackerjack/agents/coordinator.py +2 -148
- crackerjack/agents/documentation_agent.py +109 -81
- crackerjack/agents/dry_agent.py +122 -97
- crackerjack/agents/formatting_agent.py +3 -16
- crackerjack/agents/import_optimization_agent.py +1174 -130
- crackerjack/agents/performance_agent.py +956 -188
- crackerjack/agents/performance_helpers.py +229 -0
- crackerjack/agents/proactive_agent.py +1 -48
- crackerjack/agents/refactoring_agent.py +516 -246
- crackerjack/agents/refactoring_helpers.py +282 -0
- crackerjack/agents/security_agent.py +393 -90
- crackerjack/agents/test_creation_agent.py +1776 -120
- crackerjack/agents/test_specialist_agent.py +59 -15
- crackerjack/agents/tracker.py +0 -102
- crackerjack/api.py +145 -37
- crackerjack/cli/handlers.py +48 -30
- crackerjack/cli/interactive.py +11 -11
- crackerjack/cli/options.py +66 -4
- crackerjack/code_cleaner.py +808 -148
- crackerjack/config/global_lock_config.py +110 -0
- crackerjack/config/hooks.py +43 -64
- crackerjack/core/async_workflow_orchestrator.py +247 -97
- crackerjack/core/autofix_coordinator.py +192 -109
- crackerjack/core/enhanced_container.py +46 -63
- crackerjack/core/file_lifecycle.py +549 -0
- crackerjack/core/performance.py +9 -8
- crackerjack/core/performance_monitor.py +395 -0
- crackerjack/core/phase_coordinator.py +281 -94
- crackerjack/core/proactive_workflow.py +9 -58
- crackerjack/core/resource_manager.py +501 -0
- crackerjack/core/service_watchdog.py +490 -0
- crackerjack/core/session_coordinator.py +4 -8
- crackerjack/core/timeout_manager.py +504 -0
- crackerjack/core/websocket_lifecycle.py +475 -0
- crackerjack/core/workflow_orchestrator.py +343 -209
- crackerjack/dynamic_config.py +47 -6
- crackerjack/errors.py +3 -4
- crackerjack/executors/async_hook_executor.py +63 -13
- crackerjack/executors/cached_hook_executor.py +14 -14
- crackerjack/executors/hook_executor.py +100 -37
- crackerjack/executors/hook_lock_manager.py +856 -0
- crackerjack/executors/individual_hook_executor.py +120 -86
- crackerjack/intelligence/__init__.py +0 -7
- crackerjack/intelligence/adaptive_learning.py +13 -86
- crackerjack/intelligence/agent_orchestrator.py +15 -78
- crackerjack/intelligence/agent_registry.py +12 -59
- crackerjack/intelligence/agent_selector.py +31 -92
- crackerjack/intelligence/integration.py +1 -41
- crackerjack/interactive.py +9 -9
- crackerjack/managers/async_hook_manager.py +25 -8
- crackerjack/managers/hook_manager.py +9 -9
- crackerjack/managers/publish_manager.py +57 -59
- crackerjack/managers/test_command_builder.py +6 -36
- crackerjack/managers/test_executor.py +9 -61
- crackerjack/managers/test_manager.py +17 -63
- crackerjack/managers/test_manager_backup.py +77 -127
- crackerjack/managers/test_progress.py +4 -23
- crackerjack/mcp/cache.py +5 -12
- crackerjack/mcp/client_runner.py +10 -10
- crackerjack/mcp/context.py +64 -6
- crackerjack/mcp/dashboard.py +14 -11
- crackerjack/mcp/enhanced_progress_monitor.py +55 -55
- crackerjack/mcp/file_monitor.py +72 -42
- crackerjack/mcp/progress_components.py +103 -84
- crackerjack/mcp/progress_monitor.py +122 -49
- crackerjack/mcp/rate_limiter.py +12 -12
- crackerjack/mcp/server_core.py +16 -22
- crackerjack/mcp/service_watchdog.py +26 -26
- crackerjack/mcp/state.py +15 -0
- crackerjack/mcp/tools/core_tools.py +95 -39
- crackerjack/mcp/tools/error_analyzer.py +6 -32
- crackerjack/mcp/tools/execution_tools.py +1 -56
- crackerjack/mcp/tools/execution_tools_backup.py +35 -131
- crackerjack/mcp/tools/intelligence_tool_registry.py +0 -36
- crackerjack/mcp/tools/intelligence_tools.py +2 -55
- crackerjack/mcp/tools/monitoring_tools.py +308 -145
- crackerjack/mcp/tools/proactive_tools.py +12 -42
- crackerjack/mcp/tools/progress_tools.py +23 -15
- crackerjack/mcp/tools/utility_tools.py +3 -40
- crackerjack/mcp/tools/workflow_executor.py +40 -60
- crackerjack/mcp/websocket/app.py +0 -3
- crackerjack/mcp/websocket/endpoints.py +206 -268
- crackerjack/mcp/websocket/jobs.py +213 -66
- crackerjack/mcp/websocket/server.py +84 -6
- crackerjack/mcp/websocket/websocket_handler.py +137 -29
- crackerjack/models/config_adapter.py +3 -16
- crackerjack/models/protocols.py +162 -3
- crackerjack/models/resource_protocols.py +454 -0
- crackerjack/models/task.py +3 -3
- crackerjack/monitoring/__init__.py +0 -0
- crackerjack/monitoring/ai_agent_watchdog.py +25 -71
- crackerjack/monitoring/regression_prevention.py +28 -87
- crackerjack/orchestration/advanced_orchestrator.py +44 -78
- crackerjack/orchestration/coverage_improvement.py +10 -60
- crackerjack/orchestration/execution_strategies.py +16 -16
- crackerjack/orchestration/test_progress_streamer.py +61 -53
- crackerjack/plugins/base.py +1 -1
- crackerjack/plugins/managers.py +22 -20
- crackerjack/py313.py +65 -21
- crackerjack/services/backup_service.py +467 -0
- crackerjack/services/bounded_status_operations.py +627 -0
- crackerjack/services/cache.py +7 -9
- crackerjack/services/config.py +35 -52
- crackerjack/services/config_integrity.py +5 -16
- crackerjack/services/config_merge.py +542 -0
- crackerjack/services/contextual_ai_assistant.py +17 -19
- crackerjack/services/coverage_ratchet.py +44 -73
- crackerjack/services/debug.py +25 -39
- crackerjack/services/dependency_monitor.py +52 -50
- crackerjack/services/enhanced_filesystem.py +14 -11
- crackerjack/services/file_hasher.py +1 -1
- crackerjack/services/filesystem.py +1 -12
- crackerjack/services/git.py +71 -47
- crackerjack/services/health_metrics.py +31 -27
- crackerjack/services/initialization.py +276 -428
- crackerjack/services/input_validator.py +760 -0
- crackerjack/services/log_manager.py +16 -16
- crackerjack/services/logging.py +7 -6
- crackerjack/services/metrics.py +43 -43
- crackerjack/services/pattern_cache.py +2 -31
- crackerjack/services/pattern_detector.py +26 -63
- crackerjack/services/performance_benchmarks.py +20 -45
- crackerjack/services/regex_patterns.py +2887 -0
- crackerjack/services/regex_utils.py +537 -0
- crackerjack/services/secure_path_utils.py +683 -0
- crackerjack/services/secure_status_formatter.py +534 -0
- crackerjack/services/secure_subprocess.py +605 -0
- crackerjack/services/security.py +47 -10
- crackerjack/services/security_logger.py +492 -0
- crackerjack/services/server_manager.py +109 -50
- crackerjack/services/smart_scheduling.py +8 -25
- crackerjack/services/status_authentication.py +603 -0
- crackerjack/services/status_security_manager.py +442 -0
- crackerjack/services/thread_safe_status_collector.py +546 -0
- crackerjack/services/tool_version_service.py +1 -23
- crackerjack/services/unified_config.py +36 -58
- crackerjack/services/validation_rate_limiter.py +269 -0
- crackerjack/services/version_checker.py +9 -40
- crackerjack/services/websocket_resource_limiter.py +572 -0
- crackerjack/slash_commands/__init__.py +52 -2
- crackerjack/tools/__init__.py +0 -0
- crackerjack/tools/validate_input_validator_patterns.py +262 -0
- crackerjack/tools/validate_regex_patterns.py +198 -0
- {crackerjack-0.31.10.dist-info → crackerjack-0.31.12.dist-info}/METADATA +197 -12
- crackerjack-0.31.12.dist-info/RECORD +178 -0
- crackerjack/cli/facade.py +0 -104
- crackerjack-0.31.10.dist-info/RECORD +0 -149
- {crackerjack-0.31.10.dist-info → crackerjack-0.31.12.dist-info}/WHEEL +0 -0
- {crackerjack-0.31.10.dist-info → crackerjack-0.31.12.dist-info}/entry_points.txt +0 -0
- {crackerjack-0.31.10.dist-info → crackerjack-0.31.12.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,8 +1,12 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from contextlib import suppress
|
|
1
3
|
from pathlib import Path
|
|
2
4
|
|
|
3
5
|
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
|
|
4
6
|
from rich.console import Console
|
|
5
7
|
|
|
8
|
+
from crackerjack.core.timeout_manager import TimeoutStrategy, get_timeout_manager
|
|
9
|
+
|
|
6
10
|
from .jobs import JobManager
|
|
7
11
|
|
|
8
12
|
console = Console()
|
|
@@ -12,58 +16,162 @@ class WebSocketHandler:
|
|
|
12
16
|
def __init__(self, job_manager: JobManager, progress_dir: Path) -> None:
|
|
13
17
|
self.job_manager = job_manager
|
|
14
18
|
self.progress_dir = progress_dir
|
|
19
|
+
self.timeout_manager = get_timeout_manager()
|
|
15
20
|
|
|
16
21
|
async def handle_connection(self, websocket: WebSocket, job_id: str) -> None:
|
|
17
22
|
if not self.job_manager.validate_job_id(job_id):
|
|
18
23
|
await websocket.close(code=1008, reason="Invalid job ID")
|
|
19
24
|
return
|
|
20
25
|
|
|
26
|
+
try:
|
|
27
|
+
# Add timeout to the entire connection handling
|
|
28
|
+
async with self.timeout_manager.timeout_context(
|
|
29
|
+
"websocket_connection",
|
|
30
|
+
timeout=3600.0, # 1 hour max connection time
|
|
31
|
+
strategy=TimeoutStrategy.GRACEFUL_DEGRADATION,
|
|
32
|
+
):
|
|
33
|
+
await self._establish_connection(websocket, job_id)
|
|
34
|
+
await self._send_initial_progress(websocket, job_id)
|
|
35
|
+
await self._handle_message_loop(websocket, job_id)
|
|
36
|
+
|
|
37
|
+
except TimeoutError:
|
|
38
|
+
await self._handle_timeout_error(websocket, job_id)
|
|
39
|
+
except WebSocketDisconnect:
|
|
40
|
+
console.print(f"[yellow]WebSocket disconnected for job: {job_id}[/yellow]")
|
|
41
|
+
except Exception as e:
|
|
42
|
+
await self._handle_connection_error(websocket, job_id, e)
|
|
43
|
+
finally:
|
|
44
|
+
await self._cleanup_connection(job_id, websocket)
|
|
45
|
+
|
|
46
|
+
async def _establish_connection(self, websocket: WebSocket, job_id: str) -> None:
|
|
47
|
+
"""Establish WebSocket connection and add to job manager."""
|
|
21
48
|
await websocket.accept()
|
|
22
49
|
self.job_manager.add_connection(job_id, websocket)
|
|
23
|
-
|
|
24
50
|
console.print(f"[green]WebSocket connected for job: {job_id}[/green]")
|
|
25
51
|
|
|
52
|
+
async def _send_initial_progress(self, websocket: WebSocket, job_id: str) -> None:
|
|
53
|
+
"""Send initial progress data to the connected WebSocket."""
|
|
26
54
|
try:
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
55
|
+
async with self.timeout_manager.timeout_context(
|
|
56
|
+
"websocket_broadcast",
|
|
57
|
+
timeout=5.0,
|
|
58
|
+
strategy=TimeoutStrategy.FAIL_FAST,
|
|
59
|
+
):
|
|
60
|
+
initial_progress = self.job_manager.get_job_progress(job_id)
|
|
61
|
+
if initial_progress:
|
|
62
|
+
await websocket.send_json(initial_progress)
|
|
63
|
+
else:
|
|
64
|
+
await websocket.send_json(
|
|
65
|
+
self._create_initial_progress_message(job_id)
|
|
66
|
+
)
|
|
67
|
+
except Exception as e:
|
|
68
|
+
console.print(
|
|
69
|
+
f"[red]Failed to send initial progress for {job_id}: {e}[/red]"
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
def _create_initial_progress_message(self, job_id: str) -> dict:
|
|
73
|
+
"""Create initial progress message for new jobs."""
|
|
74
|
+
return {
|
|
75
|
+
"job_id": job_id,
|
|
76
|
+
"status": "waiting",
|
|
77
|
+
"message": "Waiting for job to start...",
|
|
78
|
+
"overall_progress": 0,
|
|
79
|
+
"iteration": 0,
|
|
80
|
+
"max_iterations": 10,
|
|
81
|
+
"current_stage": "Initializing",
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async def _handle_message_loop(self, websocket: WebSocket, job_id: str) -> None:
|
|
85
|
+
"""Handle the main message processing loop."""
|
|
86
|
+
message_count = 0
|
|
87
|
+
max_messages = 10000 # Prevent infinite message loops
|
|
88
|
+
|
|
89
|
+
while message_count < max_messages:
|
|
90
|
+
try:
|
|
91
|
+
should_continue = await self._process_single_message(
|
|
92
|
+
websocket, job_id, message_count + 1
|
|
93
|
+
)
|
|
94
|
+
if not should_continue:
|
|
95
|
+
break
|
|
96
|
+
message_count += 1
|
|
97
|
+
except (TimeoutError, WebSocketDisconnect, Exception):
|
|
98
|
+
break
|
|
99
|
+
|
|
100
|
+
if message_count >= max_messages:
|
|
101
|
+
console.print(
|
|
102
|
+
f"[yellow]WebSocket connection limit reached for {job_id}: {max_messages} messages[/yellow]"
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
async def _process_single_message(
|
|
106
|
+
self, websocket: WebSocket, job_id: str, message_count: int
|
|
107
|
+
) -> bool:
|
|
108
|
+
"""Process a single WebSocket message. Returns False to break the loop."""
|
|
109
|
+
try:
|
|
110
|
+
# Add timeout to individual message operations
|
|
111
|
+
async with self.timeout_manager.timeout_context(
|
|
112
|
+
"websocket_message",
|
|
113
|
+
timeout=30.0, # 30 second timeout per message
|
|
114
|
+
strategy=TimeoutStrategy.FAIL_FAST,
|
|
115
|
+
):
|
|
116
|
+
# Use asyncio.wait_for for additional protection
|
|
117
|
+
data = await asyncio.wait_for(
|
|
118
|
+
websocket.receive_text(),
|
|
119
|
+
timeout=25.0, # Slightly less than timeout context
|
|
41
120
|
)
|
|
42
121
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
console.print(
|
|
47
|
-
f"[blue]Received message for {job_id}: {data[:100]}...[/blue]",
|
|
48
|
-
)
|
|
122
|
+
console.print(
|
|
123
|
+
f"[blue]Received message {message_count} for {job_id}: {data[:100]}...[/blue]",
|
|
124
|
+
)
|
|
49
125
|
|
|
50
|
-
|
|
126
|
+
# Respond with timeout protection
|
|
127
|
+
await asyncio.wait_for(
|
|
128
|
+
websocket.send_json(
|
|
51
129
|
{
|
|
52
130
|
"type": "echo",
|
|
53
131
|
"message": f"Received: {data}",
|
|
54
132
|
"job_id": job_id,
|
|
55
|
-
|
|
56
|
-
|
|
133
|
+
"message_count": message_count,
|
|
134
|
+
}
|
|
135
|
+
),
|
|
136
|
+
timeout=5.0,
|
|
137
|
+
)
|
|
57
138
|
|
|
58
|
-
|
|
59
|
-
break
|
|
139
|
+
return True
|
|
60
140
|
|
|
141
|
+
except TimeoutError:
|
|
142
|
+
console.print(
|
|
143
|
+
f"[yellow]Message timeout for {job_id} after {message_count} messages[/yellow]"
|
|
144
|
+
)
|
|
145
|
+
return False
|
|
61
146
|
except WebSocketDisconnect:
|
|
62
147
|
console.print(f"[yellow]WebSocket disconnected for job: {job_id}[/yellow]")
|
|
148
|
+
return False
|
|
63
149
|
except Exception as e:
|
|
64
|
-
console.print(f"[red]WebSocket error for job {job_id}: {e}[/red]")
|
|
65
|
-
|
|
150
|
+
console.print(f"[red]WebSocket message error for job {job_id}: {e}[/red]")
|
|
151
|
+
return False
|
|
152
|
+
|
|
153
|
+
async def _handle_timeout_error(self, websocket: WebSocket, job_id: str) -> None:
|
|
154
|
+
"""Handle timeout errors during connection."""
|
|
155
|
+
console.print(
|
|
156
|
+
f"[yellow]WebSocket connection timeout for job: {job_id}[/yellow]"
|
|
157
|
+
)
|
|
158
|
+
with suppress(Exception):
|
|
159
|
+
await websocket.close(code=1001, reason="Connection timeout")
|
|
160
|
+
|
|
161
|
+
async def _handle_connection_error(
|
|
162
|
+
self, websocket: WebSocket, job_id: str, error: Exception
|
|
163
|
+
) -> None:
|
|
164
|
+
"""Handle connection errors."""
|
|
165
|
+
console.print(f"[red]WebSocket error for job {job_id}: {error}[/red]")
|
|
166
|
+
with suppress(Exception):
|
|
167
|
+
await websocket.close(code=1011, reason="Internal error")
|
|
168
|
+
|
|
169
|
+
async def _cleanup_connection(self, job_id: str, websocket: WebSocket) -> None:
|
|
170
|
+
"""Clean up the connection."""
|
|
171
|
+
try:
|
|
66
172
|
self.job_manager.remove_connection(job_id, websocket)
|
|
173
|
+
except Exception as e:
|
|
174
|
+
console.print(f"[red]Error removing connection for {job_id}: {e}[/red]")
|
|
67
175
|
|
|
68
176
|
|
|
69
177
|
def register_websocket_routes(
|
|
@@ -73,6 +181,6 @@ def register_websocket_routes(
|
|
|
73
181
|
) -> None:
|
|
74
182
|
handler = WebSocketHandler(job_manager, progress_dir)
|
|
75
183
|
|
|
76
|
-
@app.websocket("
|
|
184
|
+
@app.websocket("/ws/progress/{job_id}")
|
|
77
185
|
async def websocket_progress_endpoint(websocket: WebSocket, job_id: str) -> None:
|
|
78
186
|
await handler.handle_connection(websocket, job_id)
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
"""Adapter for converting between the old OptionsProtocol and new WorkflowOptions.
|
|
2
|
-
|
|
3
|
-
This provides backward compatibility during the migration period.
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
1
|
import typing as t
|
|
7
2
|
|
|
8
3
|
from .config import (
|
|
@@ -21,11 +16,8 @@ from .protocols import OptionsProtocol
|
|
|
21
16
|
|
|
22
17
|
|
|
23
18
|
class OptionsAdapter:
|
|
24
|
-
"""Adapter to convert between old and new configuration formats."""
|
|
25
|
-
|
|
26
19
|
@staticmethod
|
|
27
20
|
def from_options_protocol(options: OptionsProtocol) -> WorkflowOptions:
|
|
28
|
-
"""Convert OptionsProtocol to WorkflowOptions."""
|
|
29
21
|
return WorkflowOptions(
|
|
30
22
|
cleaning=CleaningConfig(
|
|
31
23
|
clean=getattr(options, "clean", True),
|
|
@@ -60,7 +52,7 @@ class OptionsAdapter:
|
|
|
60
52
|
),
|
|
61
53
|
ai=AIConfig(
|
|
62
54
|
ai_agent=getattr(options, "ai_agent", False),
|
|
63
|
-
autofix=getattr(options, "autofix", True),
|
|
55
|
+
autofix=getattr(options, "autofix", True),
|
|
64
56
|
ai_agent_autofix=getattr(options, "ai_agent_autofix", False),
|
|
65
57
|
start_mcp_server=getattr(options, "start_mcp_server", False),
|
|
66
58
|
max_iterations=getattr(options, "max_iterations", 10),
|
|
@@ -85,18 +77,13 @@ class OptionsAdapter:
|
|
|
85
77
|
def to_options_protocol(
|
|
86
78
|
workflow_options: WorkflowOptions,
|
|
87
79
|
) -> "LegacyOptionsWrapper":
|
|
88
|
-
"""Convert WorkflowOptions back to OptionsProtocol for backward compatibility."""
|
|
89
80
|
return LegacyOptionsWrapper(workflow_options)
|
|
90
81
|
|
|
91
82
|
|
|
92
83
|
class LegacyOptionsWrapper:
|
|
93
|
-
"""Wrapper that provides OptionsProtocol interface over WorkflowOptions."""
|
|
94
|
-
|
|
95
84
|
def __init__(self, workflow_options: WorkflowOptions) -> None:
|
|
96
85
|
self._options = workflow_options
|
|
97
86
|
|
|
98
|
-
# All the original OptionsProtocol attributes, delegating to the focused configs
|
|
99
|
-
|
|
100
87
|
@property
|
|
101
88
|
def commit(self) -> bool:
|
|
102
89
|
return self._options.git.commit
|
|
@@ -223,8 +210,8 @@ class LegacyOptionsWrapper:
|
|
|
223
210
|
|
|
224
211
|
@property
|
|
225
212
|
def enterprise_batch(self) -> str | None:
|
|
226
|
-
return None
|
|
213
|
+
return None
|
|
227
214
|
|
|
228
215
|
@property
|
|
229
216
|
def monitor_dashboard(self) -> str | None:
|
|
230
|
-
return None
|
|
217
|
+
return None
|
crackerjack/models/protocols.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import subprocess
|
|
2
2
|
import typing as t
|
|
3
|
+
from pathlib import Path
|
|
3
4
|
|
|
4
5
|
|
|
5
6
|
@t.runtime_checkable
|
|
@@ -45,7 +46,11 @@ class OptionsProtocol(t.Protocol):
|
|
|
45
46
|
comp: bool = False
|
|
46
47
|
enterprise_batch: str | None = None
|
|
47
48
|
monitor_dashboard: str | None = None
|
|
48
|
-
|
|
49
|
+
skip_config_merge: bool = False
|
|
50
|
+
disable_global_locks: bool = False
|
|
51
|
+
global_lock_timeout: int = 600
|
|
52
|
+
global_lock_cleanup: bool = True
|
|
53
|
+
global_lock_dir: str | None = None
|
|
49
54
|
|
|
50
55
|
|
|
51
56
|
@t.runtime_checkable
|
|
@@ -98,11 +103,11 @@ class HookManager(t.Protocol):
|
|
|
98
103
|
class TestManagerProtocol(t.Protocol):
|
|
99
104
|
def run_tests(self, options: OptionsProtocol) -> bool: ...
|
|
100
105
|
|
|
101
|
-
def
|
|
106
|
+
def get_test_failures(self) -> list[str]: ...
|
|
102
107
|
|
|
103
108
|
def validate_test_environment(self) -> bool: ...
|
|
104
109
|
|
|
105
|
-
def
|
|
110
|
+
def get_coverage(self) -> dict[str, t.Any]: ...
|
|
106
111
|
|
|
107
112
|
|
|
108
113
|
@t.runtime_checkable
|
|
@@ -116,3 +121,157 @@ class PublishManager(t.Protocol):
|
|
|
116
121
|
def create_git_tag(self, version: str) -> bool: ...
|
|
117
122
|
|
|
118
123
|
def cleanup_old_releases(self, keep_releases: int) -> None: ...
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@t.runtime_checkable
|
|
127
|
+
class ConfigMergeServiceProtocol(t.Protocol):
|
|
128
|
+
"""Protocol for smart configuration file merging."""
|
|
129
|
+
|
|
130
|
+
def smart_merge_pyproject(
|
|
131
|
+
self,
|
|
132
|
+
source_content: dict[str, t.Any],
|
|
133
|
+
target_path: str | t.Any,
|
|
134
|
+
project_name: str,
|
|
135
|
+
) -> dict[str, t.Any]: ...
|
|
136
|
+
|
|
137
|
+
def smart_merge_pre_commit_config(
|
|
138
|
+
self,
|
|
139
|
+
source_content: dict[str, t.Any],
|
|
140
|
+
target_path: str | t.Any,
|
|
141
|
+
project_name: str,
|
|
142
|
+
) -> dict[str, t.Any]: ...
|
|
143
|
+
|
|
144
|
+
def smart_append_file(
|
|
145
|
+
self,
|
|
146
|
+
source_content: str,
|
|
147
|
+
target_path: str | t.Any,
|
|
148
|
+
start_marker: str,
|
|
149
|
+
end_marker: str,
|
|
150
|
+
force: bool = False,
|
|
151
|
+
) -> str: ...
|
|
152
|
+
|
|
153
|
+
def smart_merge_gitignore(
|
|
154
|
+
self,
|
|
155
|
+
patterns: list[str],
|
|
156
|
+
target_path: str | t.Any,
|
|
157
|
+
) -> str: ...
|
|
158
|
+
|
|
159
|
+
def write_pyproject_config(
|
|
160
|
+
self,
|
|
161
|
+
config: dict[str, t.Any],
|
|
162
|
+
target_path: str | t.Any,
|
|
163
|
+
) -> None: ...
|
|
164
|
+
|
|
165
|
+
def write_pre_commit_config(
|
|
166
|
+
self,
|
|
167
|
+
config: dict[str, t.Any],
|
|
168
|
+
target_path: str | t.Any,
|
|
169
|
+
) -> None: ...
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
@t.runtime_checkable
|
|
173
|
+
class HookLockManagerProtocol(t.Protocol):
|
|
174
|
+
"""Protocol for managing hook-specific locks to prevent concurrent execution."""
|
|
175
|
+
|
|
176
|
+
def requires_lock(self, hook_name: str) -> bool:
|
|
177
|
+
"""Check if a hook requires sequential execution.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
hook_name: Name of the hook to check
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
True if the hook requires a lock for sequential execution
|
|
184
|
+
"""
|
|
185
|
+
...
|
|
186
|
+
|
|
187
|
+
async def acquire_hook_lock(self, hook_name: str) -> t.AsyncContextManager[None]:
|
|
188
|
+
"""Acquire a lock for the specified hook if it requires one.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
hook_name: Name of the hook to lock
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
Async context manager for lock acquisition
|
|
195
|
+
"""
|
|
196
|
+
...
|
|
197
|
+
|
|
198
|
+
def get_lock_stats(self) -> dict[str, t.Any]:
|
|
199
|
+
"""Get statistics about lock usage for monitoring.
|
|
200
|
+
|
|
201
|
+
Returns:
|
|
202
|
+
Dict containing lock statistics per hook
|
|
203
|
+
"""
|
|
204
|
+
...
|
|
205
|
+
|
|
206
|
+
def add_hook_to_lock_list(self, hook_name: str) -> None:
|
|
207
|
+
"""Add a hook to the list requiring sequential execution.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
hook_name: Name of the hook to add
|
|
211
|
+
"""
|
|
212
|
+
...
|
|
213
|
+
|
|
214
|
+
def remove_hook_from_lock_list(self, hook_name: str) -> None:
|
|
215
|
+
"""Remove a hook from the list requiring sequential execution.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
hook_name: Name of the hook to remove
|
|
219
|
+
"""
|
|
220
|
+
...
|
|
221
|
+
|
|
222
|
+
def is_hook_currently_locked(self, hook_name: str) -> bool:
|
|
223
|
+
"""Check if a hook is currently locked.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
hook_name: Name of the hook to check
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
True if the hook is currently locked
|
|
230
|
+
"""
|
|
231
|
+
...
|
|
232
|
+
|
|
233
|
+
def enable_global_lock(self, enabled: bool = True) -> None:
|
|
234
|
+
"""Enable or disable global lock functionality.
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
enabled: Whether to enable global locking
|
|
238
|
+
"""
|
|
239
|
+
...
|
|
240
|
+
|
|
241
|
+
def is_global_lock_enabled(self) -> bool:
|
|
242
|
+
"""Check if global lock functionality is enabled.
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
True if global locking is enabled
|
|
246
|
+
"""
|
|
247
|
+
...
|
|
248
|
+
|
|
249
|
+
def get_global_lock_path(self, hook_name: str) -> Path:
|
|
250
|
+
"""Get the filesystem path for a hook's global lock file.
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
hook_name: Name of the hook
|
|
254
|
+
|
|
255
|
+
Returns:
|
|
256
|
+
Path to the lock file for the hook
|
|
257
|
+
"""
|
|
258
|
+
...
|
|
259
|
+
|
|
260
|
+
def cleanup_stale_locks(self, max_age_hours: float = 2.0) -> int:
|
|
261
|
+
"""Clean up stale lock files older than max_age_hours.
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
max_age_hours: Maximum age in hours before a lock is considered stale
|
|
265
|
+
|
|
266
|
+
Returns:
|
|
267
|
+
Number of stale locks cleaned up
|
|
268
|
+
"""
|
|
269
|
+
...
|
|
270
|
+
|
|
271
|
+
def get_global_lock_stats(self) -> dict[str, t.Any]:
|
|
272
|
+
"""Get comprehensive statistics about global lock usage.
|
|
273
|
+
|
|
274
|
+
Returns:
|
|
275
|
+
Dictionary containing global lock statistics and metrics
|
|
276
|
+
"""
|
|
277
|
+
...
|