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,6 +1,5 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import signal
|
|
3
|
-
import subprocess
|
|
4
3
|
import sys
|
|
5
4
|
import time
|
|
6
5
|
import typing as t
|
|
@@ -8,58 +7,108 @@ from pathlib import Path
|
|
|
8
7
|
|
|
9
8
|
from rich.console import Console
|
|
10
9
|
|
|
10
|
+
from .secure_subprocess import execute_secure_subprocess
|
|
11
|
+
from .security_logger import get_security_logger
|
|
12
|
+
|
|
11
13
|
|
|
12
14
|
def find_mcp_server_processes() -> list[dict[str, t.Any]]:
|
|
13
|
-
"""Find
|
|
15
|
+
"""Find running MCP server processes using secure subprocess execution."""
|
|
16
|
+
security_logger = get_security_logger()
|
|
17
|
+
|
|
14
18
|
try:
|
|
15
|
-
|
|
16
|
-
|
|
19
|
+
# Use secure subprocess execution with validation
|
|
20
|
+
result = execute_secure_subprocess(
|
|
21
|
+
command=["ps", "aux"],
|
|
17
22
|
capture_output=True,
|
|
18
23
|
text=True,
|
|
19
24
|
check=True,
|
|
25
|
+
timeout=10.0, # 10 second timeout for process listing
|
|
20
26
|
)
|
|
21
27
|
|
|
22
|
-
|
|
23
|
-
str(Path.cwd())
|
|
28
|
+
return _parse_mcp_processes(result.stdout)
|
|
24
29
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
{
|
|
33
|
-
"pid": pid,
|
|
34
|
-
"command": " ".join(parts[10:]),
|
|
35
|
-
"user": parts[0],
|
|
36
|
-
"cpu": parts[2],
|
|
37
|
-
"mem": parts[3],
|
|
38
|
-
},
|
|
39
|
-
)
|
|
40
|
-
except (ValueError, IndexError):
|
|
41
|
-
continue
|
|
30
|
+
except Exception as e:
|
|
31
|
+
security_logger.log_subprocess_failure(
|
|
32
|
+
command=["ps", "aux"],
|
|
33
|
+
exit_code=-1,
|
|
34
|
+
error_output=str(e),
|
|
35
|
+
)
|
|
36
|
+
return []
|
|
42
37
|
|
|
43
|
-
return processes
|
|
44
38
|
|
|
45
|
-
|
|
46
|
-
|
|
39
|
+
def _parse_mcp_processes(stdout: str) -> list[dict[str, t.Any]]:
|
|
40
|
+
"""Parse MCP server processes from ps command output."""
|
|
41
|
+
processes: list[dict[str, t.Any]] = []
|
|
42
|
+
str(Path.cwd())
|
|
43
|
+
|
|
44
|
+
for line in stdout.splitlines():
|
|
45
|
+
if _is_mcp_server_process(line):
|
|
46
|
+
process_info = _extract_process_info(line)
|
|
47
|
+
if process_info:
|
|
48
|
+
processes.append(process_info)
|
|
49
|
+
|
|
50
|
+
return processes
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _is_mcp_server_process(line: str) -> bool:
|
|
54
|
+
"""Check if a line represents an MCP server process."""
|
|
55
|
+
return (
|
|
56
|
+
"crackerjack" in line
|
|
57
|
+
and "--start-mcp-server" in line
|
|
58
|
+
and "python" in line.lower()
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _extract_process_info(line: str) -> dict[str, t.Any] | None:
|
|
63
|
+
"""Extract process information from a ps output line."""
|
|
64
|
+
parts = line.split()
|
|
65
|
+
if len(parts) < 11:
|
|
66
|
+
return None
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
pid = int(parts[1])
|
|
70
|
+
# Find where the command actually starts (usually after the time field)
|
|
71
|
+
command_start_index = _find_command_start_index(parts)
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
"pid": pid,
|
|
75
|
+
"command": " ".join(parts[command_start_index:]),
|
|
76
|
+
"user": parts[0],
|
|
77
|
+
"cpu": parts[2],
|
|
78
|
+
"mem": parts[3],
|
|
79
|
+
}
|
|
80
|
+
except (ValueError, IndexError):
|
|
81
|
+
return None
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _find_command_start_index(parts: list[str]) -> int:
|
|
85
|
+
"""Find the index where the command starts in ps output."""
|
|
86
|
+
command_start_index = 10
|
|
87
|
+
for i, part in enumerate(parts):
|
|
88
|
+
if part.endswith("python") or "Python" in part:
|
|
89
|
+
command_start_index = i
|
|
90
|
+
break
|
|
91
|
+
return command_start_index
|
|
47
92
|
|
|
48
93
|
|
|
49
94
|
def find_websocket_server_processes() -> list[dict[str, t.Any]]:
|
|
50
|
-
"""Find
|
|
95
|
+
"""Find running WebSocket server processes using secure subprocess execution."""
|
|
96
|
+
security_logger = get_security_logger()
|
|
97
|
+
|
|
51
98
|
try:
|
|
52
|
-
|
|
53
|
-
|
|
99
|
+
# Use secure subprocess execution with validation
|
|
100
|
+
result = execute_secure_subprocess(
|
|
101
|
+
command=["ps", "aux"],
|
|
54
102
|
capture_output=True,
|
|
55
103
|
text=True,
|
|
56
104
|
check=True,
|
|
105
|
+
timeout=10.0, # 10 second timeout for process listing
|
|
57
106
|
)
|
|
58
107
|
|
|
59
108
|
processes: list[dict[str, t.Any]] = []
|
|
60
109
|
|
|
61
110
|
for line in result.stdout.splitlines():
|
|
62
|
-
if "crackerjack" in line and "
|
|
111
|
+
if "crackerjack" in line and "- - start - websocket-server" in line:
|
|
63
112
|
parts = line.split()
|
|
64
113
|
if len(parts) >= 11:
|
|
65
114
|
try:
|
|
@@ -78,12 +127,16 @@ def find_websocket_server_processes() -> list[dict[str, t.Any]]:
|
|
|
78
127
|
|
|
79
128
|
return processes
|
|
80
129
|
|
|
81
|
-
except
|
|
130
|
+
except Exception as e:
|
|
131
|
+
security_logger.log_subprocess_failure(
|
|
132
|
+
command=["ps", "aux"],
|
|
133
|
+
exit_code=-1,
|
|
134
|
+
error_output=str(e),
|
|
135
|
+
)
|
|
82
136
|
return []
|
|
83
137
|
|
|
84
138
|
|
|
85
139
|
def stop_process(pid: int, force: bool = False) -> bool:
|
|
86
|
-
"""Stop a process by PID."""
|
|
87
140
|
try:
|
|
88
141
|
if force:
|
|
89
142
|
os.kill(pid, signal.SIGKILL)
|
|
@@ -108,14 +161,13 @@ def stop_process(pid: int, force: bool = False) -> bool:
|
|
|
108
161
|
|
|
109
162
|
|
|
110
163
|
def stop_mcp_server(console: Console | None = None) -> bool:
|
|
111
|
-
"""Stop all MCP server processes."""
|
|
112
164
|
if console is None:
|
|
113
165
|
console = Console()
|
|
114
166
|
|
|
115
167
|
processes = find_mcp_server_processes()
|
|
116
168
|
|
|
117
169
|
if not processes:
|
|
118
|
-
console.print("[yellow]⚠️ No MCP server processes found[/yellow]")
|
|
170
|
+
console.print("[yellow]⚠️ No MCP server processes found[/ yellow]")
|
|
119
171
|
return True
|
|
120
172
|
|
|
121
173
|
success = True
|
|
@@ -131,14 +183,13 @@ def stop_mcp_server(console: Console | None = None) -> bool:
|
|
|
131
183
|
|
|
132
184
|
|
|
133
185
|
def stop_websocket_server(console: Console | None = None) -> bool:
|
|
134
|
-
"""Stop all WebSocket server processes."""
|
|
135
186
|
if console is None:
|
|
136
187
|
console = Console()
|
|
137
188
|
|
|
138
189
|
processes = find_websocket_server_processes()
|
|
139
190
|
|
|
140
191
|
if not processes:
|
|
141
|
-
console.print("[yellow]⚠️ No WebSocket server processes found[/yellow]")
|
|
192
|
+
console.print("[yellow]⚠️ No WebSocket server processes found[/ yellow]")
|
|
142
193
|
return True
|
|
143
194
|
|
|
144
195
|
success = True
|
|
@@ -154,7 +205,6 @@ def stop_websocket_server(console: Console | None = None) -> bool:
|
|
|
154
205
|
|
|
155
206
|
|
|
156
207
|
def stop_all_servers(console: Console | None = None) -> bool:
|
|
157
|
-
"""Stop all crackerjack server processes."""
|
|
158
208
|
if console is None:
|
|
159
209
|
console = Console()
|
|
160
210
|
|
|
@@ -168,11 +218,10 @@ def restart_mcp_server(
|
|
|
168
218
|
websocket_port: int | None = None,
|
|
169
219
|
console: Console | None = None,
|
|
170
220
|
) -> bool:
|
|
171
|
-
"""Restart the MCP server."""
|
|
172
221
|
if console is None:
|
|
173
222
|
console = Console()
|
|
174
223
|
|
|
175
|
-
console.print("[bold cyan]🔄 Restarting MCP server...[/bold cyan]")
|
|
224
|
+
console.print("[bold cyan]🔄 Restarting MCP server...[/ bold cyan]")
|
|
176
225
|
|
|
177
226
|
stop_mcp_server(console)
|
|
178
227
|
|
|
@@ -181,10 +230,14 @@ def restart_mcp_server(
|
|
|
181
230
|
|
|
182
231
|
console.print("🚀 Starting new MCP server...")
|
|
183
232
|
try:
|
|
233
|
+
# Build command with proper argument formatting
|
|
184
234
|
cmd = [sys.executable, "-m", "crackerjack", "--start-mcp-server"]
|
|
185
235
|
if websocket_port:
|
|
186
236
|
cmd.extend(["--websocket-port", str(websocket_port)])
|
|
187
237
|
|
|
238
|
+
# Use secure subprocess execution for server restart
|
|
239
|
+
import subprocess
|
|
240
|
+
|
|
188
241
|
subprocess.Popen(
|
|
189
242
|
cmd,
|
|
190
243
|
stdout=subprocess.DEVNULL,
|
|
@@ -192,6 +245,13 @@ def restart_mcp_server(
|
|
|
192
245
|
start_new_session=True,
|
|
193
246
|
)
|
|
194
247
|
|
|
248
|
+
# Log the secure server start
|
|
249
|
+
security_logger = get_security_logger()
|
|
250
|
+
security_logger.log_subprocess_execution(
|
|
251
|
+
command=cmd,
|
|
252
|
+
purpose="mcp_server_restart",
|
|
253
|
+
)
|
|
254
|
+
|
|
195
255
|
console.print("✅ MCP server restart initiated")
|
|
196
256
|
return True
|
|
197
257
|
|
|
@@ -201,34 +261,33 @@ def restart_mcp_server(
|
|
|
201
261
|
|
|
202
262
|
|
|
203
263
|
def list_server_status(console: Console | None = None) -> None:
|
|
204
|
-
"""List status of all crackerjack servers."""
|
|
205
264
|
if console is None:
|
|
206
265
|
console = Console()
|
|
207
266
|
|
|
208
|
-
console.print("[bold cyan]📊 Crackerjack Server Status[/bold cyan]")
|
|
267
|
+
console.print("[bold cyan]📊 Crackerjack Server Status[/ bold cyan]")
|
|
209
268
|
|
|
210
269
|
mcp_processes = find_mcp_server_processes()
|
|
211
270
|
websocket_processes = find_websocket_server_processes()
|
|
212
271
|
|
|
213
272
|
if mcp_processes:
|
|
214
|
-
console.print("\n[bold green]MCP Servers:[/bold green]")
|
|
273
|
+
console.print("\n[bold green]MCP Servers: [/ bold green]")
|
|
215
274
|
for proc in mcp_processes:
|
|
216
275
|
console.print(
|
|
217
|
-
f"
|
|
276
|
+
f" • PID {proc['pid']} - CPU: {proc['cpu']}%-Memory: {proc['mem']}%",
|
|
218
277
|
)
|
|
219
|
-
console.print(f"
|
|
278
|
+
console.print(f" Command: {proc['command']}")
|
|
220
279
|
else:
|
|
221
|
-
console.print("\n[yellow]MCP Servers: None running[/yellow]")
|
|
280
|
+
console.print("\n[yellow]MCP Servers: None running[/ yellow]")
|
|
222
281
|
|
|
223
282
|
if websocket_processes:
|
|
224
|
-
console.print("\n[bold green]WebSocket Servers:[/bold green]")
|
|
283
|
+
console.print("\n[bold green]WebSocket Servers: [/ bold green]")
|
|
225
284
|
for proc in websocket_processes:
|
|
226
285
|
console.print(
|
|
227
|
-
f"
|
|
286
|
+
f" • PID {proc['pid']} - CPU: {proc['cpu']}%-Memory: {proc['mem']}%",
|
|
228
287
|
)
|
|
229
|
-
console.print(f"
|
|
288
|
+
console.print(f" Command: {proc['command']}")
|
|
230
289
|
else:
|
|
231
|
-
console.print("\n[yellow]WebSocket Servers: None running[/yellow]")
|
|
290
|
+
console.print("\n[yellow]WebSocket Servers: None running[/ yellow]")
|
|
232
291
|
|
|
233
292
|
if not mcp_processes and not websocket_processes:
|
|
234
|
-
console.print("\n[dim]No crackerjack servers currently running[/dim]")
|
|
293
|
+
console.print("\n[dim]No crackerjack servers currently running[/ dim]")
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
"""Smart scheduling service for automated initialization.
|
|
2
|
-
|
|
3
|
-
This module handles intelligent scheduling of crackerjack initialization based on
|
|
4
|
-
various triggers like time, commits, or activity. Split from tool_version_service.py.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
1
|
import os
|
|
8
2
|
import subprocess
|
|
9
3
|
from datetime import datetime, timedelta
|
|
@@ -13,8 +7,6 @@ from rich.console import Console
|
|
|
13
7
|
|
|
14
8
|
|
|
15
9
|
class SmartSchedulingService:
|
|
16
|
-
"""Service for intelligent scheduling of crackerjack operations."""
|
|
17
|
-
|
|
18
10
|
def __init__(self, console: Console, project_path: Path) -> None:
|
|
19
11
|
self.console = console
|
|
20
12
|
self.project_path = project_path
|
|
@@ -22,7 +14,6 @@ class SmartSchedulingService:
|
|
|
22
14
|
self.cache_dir.mkdir(parents=True, exist_ok=True)
|
|
23
15
|
|
|
24
16
|
def should_scheduled_init(self) -> bool:
|
|
25
|
-
"""Check if initialization should run based on configured schedule."""
|
|
26
17
|
init_schedule = os.environ.get("CRACKERJACK_INIT_SCHEDULE", "weekly")
|
|
27
18
|
|
|
28
19
|
if init_schedule == "disabled":
|
|
@@ -38,56 +29,51 @@ class SmartSchedulingService:
|
|
|
38
29
|
return self._check_weekly_schedule()
|
|
39
30
|
|
|
40
31
|
def record_init_timestamp(self) -> None:
|
|
41
|
-
"""Record the current timestamp as the last initialization time."""
|
|
42
32
|
timestamp_file = self.cache_dir / f"{self.project_path.name}.init_timestamp"
|
|
43
33
|
try:
|
|
44
34
|
timestamp_file.write_text(datetime.now().isoformat())
|
|
45
35
|
except OSError as e:
|
|
46
36
|
self.console.print(
|
|
47
|
-
f"[yellow]⚠️ Could not record init timestamp: {e}[/yellow]",
|
|
37
|
+
f"[yellow]⚠️ Could not record init timestamp: {e}[/ yellow]",
|
|
48
38
|
)
|
|
49
39
|
|
|
50
40
|
def _check_weekly_schedule(self) -> bool:
|
|
51
|
-
"""Check if weekly initialization is due."""
|
|
52
41
|
init_day = os.environ.get("CRACKERJACK_INIT_DAY", "monday")
|
|
53
|
-
today = datetime.now().strftime("%A").lower()
|
|
42
|
+
today = datetime.now().strftime("% A").lower()
|
|
54
43
|
|
|
55
44
|
if today == init_day.lower():
|
|
56
45
|
last_init = self._get_last_init_timestamp()
|
|
57
46
|
if datetime.now() - last_init > timedelta(days=6):
|
|
58
47
|
self.console.print(
|
|
59
|
-
f"[blue]📅 Weekly initialization scheduled for {init_day}[/blue]",
|
|
48
|
+
f"[blue]📅 Weekly initialization scheduled for {init_day}[/ blue]",
|
|
60
49
|
)
|
|
61
50
|
return True
|
|
62
51
|
|
|
63
52
|
return False
|
|
64
53
|
|
|
65
54
|
def _check_commit_based_schedule(self) -> bool:
|
|
66
|
-
"""Check if initialization is due based on commit count."""
|
|
67
55
|
commits_since_init = self._count_commits_since_init()
|
|
68
56
|
threshold = int(os.environ.get("CRACKERJACK_INIT_COMMITS", "50"))
|
|
69
57
|
|
|
70
58
|
if commits_since_init >= threshold:
|
|
71
59
|
self.console.print(
|
|
72
60
|
f"[blue]📊 {commits_since_init} commits since last init "
|
|
73
|
-
f"(threshold: {threshold})[/blue]",
|
|
61
|
+
f"(threshold: {threshold})[/ blue]",
|
|
74
62
|
)
|
|
75
63
|
return True
|
|
76
64
|
|
|
77
65
|
return False
|
|
78
66
|
|
|
79
67
|
def _check_activity_based_schedule(self) -> bool:
|
|
80
|
-
"""Check if initialization is due based on recent activity."""
|
|
81
68
|
if self._has_recent_activity() and self._days_since_init() >= 7:
|
|
82
69
|
self.console.print(
|
|
83
|
-
"[blue]⚡ Recent activity detected, initialization recommended[/blue]",
|
|
70
|
+
"[blue]⚡ Recent activity detected, initialization recommended[/ blue]",
|
|
84
71
|
)
|
|
85
72
|
return True
|
|
86
73
|
|
|
87
74
|
return False
|
|
88
75
|
|
|
89
76
|
def _get_last_init_timestamp(self) -> datetime:
|
|
90
|
-
"""Get the timestamp of the last initialization."""
|
|
91
77
|
timestamp_file = self.cache_dir / f"{self.project_path.name}.init_timestamp"
|
|
92
78
|
|
|
93
79
|
if timestamp_file.exists():
|
|
@@ -100,12 +86,11 @@ class SmartSchedulingService:
|
|
|
100
86
|
return datetime.now() - timedelta(days=30)
|
|
101
87
|
|
|
102
88
|
def _count_commits_since_init(self) -> int:
|
|
103
|
-
"
|
|
104
|
-
since_date = self._get_last_init_timestamp().strftime("%Y-%m-%d")
|
|
89
|
+
since_date = self._get_last_init_timestamp().strftime("% Y - % m - % d")
|
|
105
90
|
|
|
106
91
|
try:
|
|
107
92
|
result = subprocess.run(
|
|
108
|
-
["git", "log", f"--since={since_date}", "--oneline"],
|
|
93
|
+
["git", "log", f"--since ={since_date}", "--oneline"],
|
|
109
94
|
cwd=self.project_path,
|
|
110
95
|
capture_output=True,
|
|
111
96
|
text=True,
|
|
@@ -122,10 +107,9 @@ class SmartSchedulingService:
|
|
|
122
107
|
return 0
|
|
123
108
|
|
|
124
109
|
def _has_recent_activity(self) -> bool:
|
|
125
|
-
"""Check if there has been recent git activity."""
|
|
126
110
|
try:
|
|
127
111
|
result = subprocess.run(
|
|
128
|
-
["git", "log", "-1", "--since=24.hours", "--oneline"],
|
|
112
|
+
["git", "log", "- 1", "--since=24.hours", "--oneline"],
|
|
129
113
|
cwd=self.project_path,
|
|
130
114
|
capture_output=True,
|
|
131
115
|
text=True,
|
|
@@ -139,6 +123,5 @@ class SmartSchedulingService:
|
|
|
139
123
|
return False
|
|
140
124
|
|
|
141
125
|
def _days_since_init(self) -> int:
|
|
142
|
-
"""Calculate days since last initialization."""
|
|
143
126
|
last_init = self._get_last_init_timestamp()
|
|
144
127
|
return (datetime.now() - last_init).days
|