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.

Files changed (155) hide show
  1. crackerjack/CLAUDE.md +288 -705
  2. crackerjack/__main__.py +22 -8
  3. crackerjack/agents/__init__.py +0 -3
  4. crackerjack/agents/architect_agent.py +0 -43
  5. crackerjack/agents/base.py +1 -9
  6. crackerjack/agents/coordinator.py +2 -148
  7. crackerjack/agents/documentation_agent.py +109 -81
  8. crackerjack/agents/dry_agent.py +122 -97
  9. crackerjack/agents/formatting_agent.py +3 -16
  10. crackerjack/agents/import_optimization_agent.py +1174 -130
  11. crackerjack/agents/performance_agent.py +956 -188
  12. crackerjack/agents/performance_helpers.py +229 -0
  13. crackerjack/agents/proactive_agent.py +1 -48
  14. crackerjack/agents/refactoring_agent.py +516 -246
  15. crackerjack/agents/refactoring_helpers.py +282 -0
  16. crackerjack/agents/security_agent.py +393 -90
  17. crackerjack/agents/test_creation_agent.py +1776 -120
  18. crackerjack/agents/test_specialist_agent.py +59 -15
  19. crackerjack/agents/tracker.py +0 -102
  20. crackerjack/api.py +145 -37
  21. crackerjack/cli/handlers.py +48 -30
  22. crackerjack/cli/interactive.py +11 -11
  23. crackerjack/cli/options.py +66 -4
  24. crackerjack/code_cleaner.py +808 -148
  25. crackerjack/config/global_lock_config.py +110 -0
  26. crackerjack/config/hooks.py +43 -64
  27. crackerjack/core/async_workflow_orchestrator.py +247 -97
  28. crackerjack/core/autofix_coordinator.py +192 -109
  29. crackerjack/core/enhanced_container.py +46 -63
  30. crackerjack/core/file_lifecycle.py +549 -0
  31. crackerjack/core/performance.py +9 -8
  32. crackerjack/core/performance_monitor.py +395 -0
  33. crackerjack/core/phase_coordinator.py +281 -94
  34. crackerjack/core/proactive_workflow.py +9 -58
  35. crackerjack/core/resource_manager.py +501 -0
  36. crackerjack/core/service_watchdog.py +490 -0
  37. crackerjack/core/session_coordinator.py +4 -8
  38. crackerjack/core/timeout_manager.py +504 -0
  39. crackerjack/core/websocket_lifecycle.py +475 -0
  40. crackerjack/core/workflow_orchestrator.py +343 -209
  41. crackerjack/dynamic_config.py +47 -6
  42. crackerjack/errors.py +3 -4
  43. crackerjack/executors/async_hook_executor.py +63 -13
  44. crackerjack/executors/cached_hook_executor.py +14 -14
  45. crackerjack/executors/hook_executor.py +100 -37
  46. crackerjack/executors/hook_lock_manager.py +856 -0
  47. crackerjack/executors/individual_hook_executor.py +120 -86
  48. crackerjack/intelligence/__init__.py +0 -7
  49. crackerjack/intelligence/adaptive_learning.py +13 -86
  50. crackerjack/intelligence/agent_orchestrator.py +15 -78
  51. crackerjack/intelligence/agent_registry.py +12 -59
  52. crackerjack/intelligence/agent_selector.py +31 -92
  53. crackerjack/intelligence/integration.py +1 -41
  54. crackerjack/interactive.py +9 -9
  55. crackerjack/managers/async_hook_manager.py +25 -8
  56. crackerjack/managers/hook_manager.py +9 -9
  57. crackerjack/managers/publish_manager.py +57 -59
  58. crackerjack/managers/test_command_builder.py +6 -36
  59. crackerjack/managers/test_executor.py +9 -61
  60. crackerjack/managers/test_manager.py +17 -63
  61. crackerjack/managers/test_manager_backup.py +77 -127
  62. crackerjack/managers/test_progress.py +4 -23
  63. crackerjack/mcp/cache.py +5 -12
  64. crackerjack/mcp/client_runner.py +10 -10
  65. crackerjack/mcp/context.py +64 -6
  66. crackerjack/mcp/dashboard.py +14 -11
  67. crackerjack/mcp/enhanced_progress_monitor.py +55 -55
  68. crackerjack/mcp/file_monitor.py +72 -42
  69. crackerjack/mcp/progress_components.py +103 -84
  70. crackerjack/mcp/progress_monitor.py +122 -49
  71. crackerjack/mcp/rate_limiter.py +12 -12
  72. crackerjack/mcp/server_core.py +16 -22
  73. crackerjack/mcp/service_watchdog.py +26 -26
  74. crackerjack/mcp/state.py +15 -0
  75. crackerjack/mcp/tools/core_tools.py +95 -39
  76. crackerjack/mcp/tools/error_analyzer.py +6 -32
  77. crackerjack/mcp/tools/execution_tools.py +1 -56
  78. crackerjack/mcp/tools/execution_tools_backup.py +35 -131
  79. crackerjack/mcp/tools/intelligence_tool_registry.py +0 -36
  80. crackerjack/mcp/tools/intelligence_tools.py +2 -55
  81. crackerjack/mcp/tools/monitoring_tools.py +308 -145
  82. crackerjack/mcp/tools/proactive_tools.py +12 -42
  83. crackerjack/mcp/tools/progress_tools.py +23 -15
  84. crackerjack/mcp/tools/utility_tools.py +3 -40
  85. crackerjack/mcp/tools/workflow_executor.py +40 -60
  86. crackerjack/mcp/websocket/app.py +0 -3
  87. crackerjack/mcp/websocket/endpoints.py +206 -268
  88. crackerjack/mcp/websocket/jobs.py +213 -66
  89. crackerjack/mcp/websocket/server.py +84 -6
  90. crackerjack/mcp/websocket/websocket_handler.py +137 -29
  91. crackerjack/models/config_adapter.py +3 -16
  92. crackerjack/models/protocols.py +162 -3
  93. crackerjack/models/resource_protocols.py +454 -0
  94. crackerjack/models/task.py +3 -3
  95. crackerjack/monitoring/__init__.py +0 -0
  96. crackerjack/monitoring/ai_agent_watchdog.py +25 -71
  97. crackerjack/monitoring/regression_prevention.py +28 -87
  98. crackerjack/orchestration/advanced_orchestrator.py +44 -78
  99. crackerjack/orchestration/coverage_improvement.py +10 -60
  100. crackerjack/orchestration/execution_strategies.py +16 -16
  101. crackerjack/orchestration/test_progress_streamer.py +61 -53
  102. crackerjack/plugins/base.py +1 -1
  103. crackerjack/plugins/managers.py +22 -20
  104. crackerjack/py313.py +65 -21
  105. crackerjack/services/backup_service.py +467 -0
  106. crackerjack/services/bounded_status_operations.py +627 -0
  107. crackerjack/services/cache.py +7 -9
  108. crackerjack/services/config.py +35 -52
  109. crackerjack/services/config_integrity.py +5 -16
  110. crackerjack/services/config_merge.py +542 -0
  111. crackerjack/services/contextual_ai_assistant.py +17 -19
  112. crackerjack/services/coverage_ratchet.py +44 -73
  113. crackerjack/services/debug.py +25 -39
  114. crackerjack/services/dependency_monitor.py +52 -50
  115. crackerjack/services/enhanced_filesystem.py +14 -11
  116. crackerjack/services/file_hasher.py +1 -1
  117. crackerjack/services/filesystem.py +1 -12
  118. crackerjack/services/git.py +71 -47
  119. crackerjack/services/health_metrics.py +31 -27
  120. crackerjack/services/initialization.py +276 -428
  121. crackerjack/services/input_validator.py +760 -0
  122. crackerjack/services/log_manager.py +16 -16
  123. crackerjack/services/logging.py +7 -6
  124. crackerjack/services/metrics.py +43 -43
  125. crackerjack/services/pattern_cache.py +2 -31
  126. crackerjack/services/pattern_detector.py +26 -63
  127. crackerjack/services/performance_benchmarks.py +20 -45
  128. crackerjack/services/regex_patterns.py +2887 -0
  129. crackerjack/services/regex_utils.py +537 -0
  130. crackerjack/services/secure_path_utils.py +683 -0
  131. crackerjack/services/secure_status_formatter.py +534 -0
  132. crackerjack/services/secure_subprocess.py +605 -0
  133. crackerjack/services/security.py +47 -10
  134. crackerjack/services/security_logger.py +492 -0
  135. crackerjack/services/server_manager.py +109 -50
  136. crackerjack/services/smart_scheduling.py +8 -25
  137. crackerjack/services/status_authentication.py +603 -0
  138. crackerjack/services/status_security_manager.py +442 -0
  139. crackerjack/services/thread_safe_status_collector.py +546 -0
  140. crackerjack/services/tool_version_service.py +1 -23
  141. crackerjack/services/unified_config.py +36 -58
  142. crackerjack/services/validation_rate_limiter.py +269 -0
  143. crackerjack/services/version_checker.py +9 -40
  144. crackerjack/services/websocket_resource_limiter.py +572 -0
  145. crackerjack/slash_commands/__init__.py +52 -2
  146. crackerjack/tools/__init__.py +0 -0
  147. crackerjack/tools/validate_input_validator_patterns.py +262 -0
  148. crackerjack/tools/validate_regex_patterns.py +198 -0
  149. {crackerjack-0.31.10.dist-info → crackerjack-0.31.12.dist-info}/METADATA +197 -12
  150. crackerjack-0.31.12.dist-info/RECORD +178 -0
  151. crackerjack/cli/facade.py +0 -104
  152. crackerjack-0.31.10.dist-info/RECORD +0 -149
  153. {crackerjack-0.31.10.dist-info → crackerjack-0.31.12.dist-info}/WHEEL +0 -0
  154. {crackerjack-0.31.10.dist-info → crackerjack-0.31.12.dist-info}/entry_points.txt +0 -0
  155. {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 all running MCP server processes for this project."""
15
+ """Find running MCP server processes using secure subprocess execution."""
16
+ security_logger = get_security_logger()
17
+
14
18
  try:
15
- result = subprocess.run(
16
- ["ps", "aux"],
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
- processes: list[dict[str, t.Any]] = []
23
- str(Path.cwd())
28
+ return _parse_mcp_processes(result.stdout)
24
29
 
25
- for line in result.stdout.splitlines():
26
- if "crackerjack" in line and "--start-mcp-server" in line:
27
- parts = line.split()
28
- if len(parts) >= 11:
29
- try:
30
- pid = int(parts[1])
31
- processes.append(
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
- except (subprocess.CalledProcessError, FileNotFoundError):
46
- return []
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 all running WebSocket server processes for this project."""
95
+ """Find running WebSocket server processes using secure subprocess execution."""
96
+ security_logger = get_security_logger()
97
+
51
98
  try:
52
- result = subprocess.run(
53
- ["ps", "aux"],
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 "--start-websocket-server" in line:
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 (subprocess.CalledProcessError, FileNotFoundError):
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" • PID {proc['pid']} - CPU: {proc['cpu']}% - Memory: {proc['mem']}%",
276
+ f" • PID {proc['pid']} - CPU: {proc['cpu']}%-Memory: {proc['mem']}%",
218
277
  )
219
- console.print(f" Command: {proc['command']}")
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" • PID {proc['pid']} - CPU: {proc['cpu']}% - Memory: {proc['mem']}%",
286
+ f" • PID {proc['pid']} - CPU: {proc['cpu']}%-Memory: {proc['mem']}%",
228
287
  )
229
- console.print(f" Command: {proc['command']}")
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
- """Count git commits since last initialization."""
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