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
crackerjack/services/git.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import subprocess
|
|
2
|
+
import typing as t
|
|
2
3
|
from pathlib import Path
|
|
3
4
|
|
|
4
5
|
from rich.console import Console
|
|
@@ -6,10 +7,23 @@ from rich.console import Console
|
|
|
6
7
|
from .secure_subprocess import execute_secure_subprocess
|
|
7
8
|
from .security_logger import get_security_logger
|
|
8
9
|
|
|
10
|
+
GIT_COMMANDS = {
|
|
11
|
+
"git_dir": ["rev-parse", "--git-dir"],
|
|
12
|
+
"staged_files": ["diff", "--cached", "--name-only", "--diff-filter=ACMRT"],
|
|
13
|
+
"unstaged_files": ["diff", "--name-only", "--diff-filter=ACMRT"],
|
|
14
|
+
"untracked_files": ["ls-files", "--others", "--exclude-standard"],
|
|
15
|
+
"staged_files_simple": ["diff", "--cached", "--name-only"],
|
|
16
|
+
"add_file": ["add"],
|
|
17
|
+
"add_all": ["add", "-A", "."],
|
|
18
|
+
"commit": ["commit", "-m"],
|
|
19
|
+
"add_updated": ["add", "-u"],
|
|
20
|
+
"push_porcelain": ["push", "--porcelain"],
|
|
21
|
+
"current_branch": ["branch", "--show-current"],
|
|
22
|
+
"commits_ahead": ["rev-list[t.Any]", "--count", "@{u}..HEAD"],
|
|
23
|
+
}
|
|
9
24
|
|
|
10
|
-
class FailedGitResult:
|
|
11
|
-
"""A Git result object compatible with subprocess.CompletedProcess."""
|
|
12
25
|
|
|
26
|
+
class FailedGitResult:
|
|
13
27
|
def __init__(self, command: list[str], error: str) -> None:
|
|
14
28
|
self.args = command
|
|
15
29
|
self.returncode = -1
|
|
@@ -25,7 +39,6 @@ class GitService:
|
|
|
25
39
|
def _run_git_command(
|
|
26
40
|
self, args: list[str]
|
|
27
41
|
) -> subprocess.CompletedProcess[str] | FailedGitResult:
|
|
28
|
-
"""Execute Git commands with secure subprocess validation."""
|
|
29
42
|
cmd = ["git", *args]
|
|
30
43
|
|
|
31
44
|
try:
|
|
@@ -35,10 +48,9 @@ class GitService:
|
|
|
35
48
|
capture_output=True,
|
|
36
49
|
text=True,
|
|
37
50
|
timeout=60,
|
|
38
|
-
check=False,
|
|
51
|
+
check=False,
|
|
39
52
|
)
|
|
40
53
|
except Exception as e:
|
|
41
|
-
# Log security issues but return a compatible result
|
|
42
54
|
security_logger = get_security_logger()
|
|
43
55
|
security_logger.log_subprocess_failure(
|
|
44
56
|
command=cmd,
|
|
@@ -46,46 +58,39 @@ class GitService:
|
|
|
46
58
|
error_output=str(e),
|
|
47
59
|
)
|
|
48
60
|
|
|
49
|
-
# Create compatible result for Git operations
|
|
50
61
|
return FailedGitResult(cmd, str(e))
|
|
51
62
|
|
|
52
63
|
def is_git_repo(self) -> bool:
|
|
53
64
|
try:
|
|
54
|
-
result = self._run_git_command(["
|
|
65
|
+
result = self._run_git_command(GIT_COMMANDS["git_dir"])
|
|
55
66
|
return result.returncode == 0
|
|
56
67
|
except (subprocess.SubprocessError, OSError, FileNotFoundError):
|
|
57
68
|
return False
|
|
58
69
|
|
|
59
70
|
def get_changed_files(self) -> list[str]:
|
|
60
71
|
try:
|
|
61
|
-
staged_result = self._run_git_command(
|
|
62
|
-
["diff", "--cached", "- - name-only", "- - diff-filter=ACMRT"]
|
|
63
|
-
)
|
|
72
|
+
staged_result = self._run_git_command(GIT_COMMANDS["staged_files"])
|
|
64
73
|
staged_files = (
|
|
65
74
|
staged_result.stdout.strip().split("\n")
|
|
66
75
|
if staged_result.stdout.strip()
|
|
67
76
|
else []
|
|
68
77
|
)
|
|
69
78
|
|
|
70
|
-
unstaged_result = self._run_git_command(
|
|
71
|
-
["diff", "- - name-only", "- - diff-filter=ACMRT"]
|
|
72
|
-
)
|
|
79
|
+
unstaged_result = self._run_git_command(GIT_COMMANDS["unstaged_files"])
|
|
73
80
|
unstaged_files = (
|
|
74
81
|
unstaged_result.stdout.strip().split("\n")
|
|
75
82
|
if unstaged_result.stdout.strip()
|
|
76
83
|
else []
|
|
77
84
|
)
|
|
78
85
|
|
|
79
|
-
untracked_result = self._run_git_command(
|
|
80
|
-
["ls-files", "--others", "- - exclude-standard"],
|
|
81
|
-
)
|
|
86
|
+
untracked_result = self._run_git_command(GIT_COMMANDS["untracked_files"])
|
|
82
87
|
untracked_files = (
|
|
83
88
|
untracked_result.stdout.strip().split("\n")
|
|
84
89
|
if untracked_result.stdout.strip()
|
|
85
90
|
else []
|
|
86
91
|
)
|
|
87
92
|
|
|
88
|
-
all_files = set(staged_files + unstaged_files + untracked_files)
|
|
93
|
+
all_files = set[t.Any](staged_files + unstaged_files + untracked_files)
|
|
89
94
|
return [f for f in all_files if f]
|
|
90
95
|
except Exception as e:
|
|
91
96
|
self.console.print(f"[yellow]⚠️[/ yellow] Error getting changed files: {e}")
|
|
@@ -93,7 +98,7 @@ class GitService:
|
|
|
93
98
|
|
|
94
99
|
def get_staged_files(self) -> list[str]:
|
|
95
100
|
try:
|
|
96
|
-
result = self._run_git_command(["
|
|
101
|
+
result = self._run_git_command(GIT_COMMANDS["staged_files_simple"])
|
|
97
102
|
return result.stdout.strip().split("\n") if result.stdout.strip() else []
|
|
98
103
|
except Exception as e:
|
|
99
104
|
self.console.print(f"[yellow]⚠️[/ yellow] Error getting staged files: {e}")
|
|
@@ -102,7 +107,8 @@ class GitService:
|
|
|
102
107
|
def add_files(self, files: list[str]) -> bool:
|
|
103
108
|
try:
|
|
104
109
|
for file in files:
|
|
105
|
-
|
|
110
|
+
cmd = GIT_COMMANDS["add_file"] + [file]
|
|
111
|
+
result = self._run_git_command(cmd)
|
|
106
112
|
if result.returncode != 0:
|
|
107
113
|
self.console.print(
|
|
108
114
|
f"[red]❌[/ red] Failed to add {file}: {result.stderr}",
|
|
@@ -114,9 +120,8 @@ class GitService:
|
|
|
114
120
|
return False
|
|
115
121
|
|
|
116
122
|
def add_all_files(self) -> bool:
|
|
117
|
-
"""Stage all changes including new, modified, and deleted files."""
|
|
118
123
|
try:
|
|
119
|
-
result = self._run_git_command(["
|
|
124
|
+
result = self._run_git_command(GIT_COMMANDS["add_all"])
|
|
120
125
|
if result.returncode == 0:
|
|
121
126
|
self.console.print("[green]✅[/ green] Staged all changes")
|
|
122
127
|
return True
|
|
@@ -130,7 +135,8 @@ class GitService:
|
|
|
130
135
|
|
|
131
136
|
def commit(self, message: str) -> bool:
|
|
132
137
|
try:
|
|
133
|
-
|
|
138
|
+
cmd = GIT_COMMANDS["commit"] + [message]
|
|
139
|
+
result = self._run_git_command(cmd)
|
|
134
140
|
if result.returncode == 0:
|
|
135
141
|
self.console.print(f"[green]✅[/ green] Committed: {message}")
|
|
136
142
|
return True
|
|
@@ -153,14 +159,15 @@ class GitService:
|
|
|
153
159
|
"[yellow]🔄[/ yellow] Pre - commit hooks modified files - attempting to re-stage and retry commit"
|
|
154
160
|
)
|
|
155
161
|
|
|
156
|
-
add_result = self._run_git_command(["
|
|
162
|
+
add_result = self._run_git_command(GIT_COMMANDS["add_updated"])
|
|
157
163
|
if add_result.returncode != 0:
|
|
158
164
|
self.console.print(
|
|
159
165
|
f"[red]❌[/ red] Failed to re-stage files: {add_result.stderr}"
|
|
160
166
|
)
|
|
161
167
|
return False
|
|
162
168
|
|
|
163
|
-
|
|
169
|
+
cmd = GIT_COMMANDS["commit"] + [message]
|
|
170
|
+
retry_result = self._run_git_command(cmd)
|
|
164
171
|
if retry_result.returncode == 0:
|
|
165
172
|
self.console.print(
|
|
166
173
|
f"[green]✅[/ green] Committed after re-staging: {message}"
|
|
@@ -187,8 +194,7 @@ class GitService:
|
|
|
187
194
|
|
|
188
195
|
def push(self) -> bool:
|
|
189
196
|
try:
|
|
190
|
-
|
|
191
|
-
result = self._run_git_command(["push", "--porcelain"])
|
|
197
|
+
result = self._run_git_command(GIT_COMMANDS["push_porcelain"])
|
|
192
198
|
if result.returncode == 0:
|
|
193
199
|
self._display_push_success(result.stdout)
|
|
194
200
|
return True
|
|
@@ -199,7 +205,6 @@ class GitService:
|
|
|
199
205
|
return False
|
|
200
206
|
|
|
201
207
|
def _display_push_success(self, push_output: str) -> None:
|
|
202
|
-
"""Display detailed push success information."""
|
|
203
208
|
lines = push_output.strip().split("\n") if push_output.strip() else []
|
|
204
209
|
|
|
205
210
|
if not lines:
|
|
@@ -210,15 +215,12 @@ class GitService:
|
|
|
210
215
|
self._display_push_results(pushed_refs)
|
|
211
216
|
|
|
212
217
|
def _display_no_commits_message(self) -> None:
|
|
213
|
-
"""Display message for no new commits."""
|
|
214
218
|
self.console.print("[green]✅[/ green] Pushed to remote (no new commits)")
|
|
215
219
|
|
|
216
220
|
def _parse_pushed_refs(self, lines: list[str]) -> list[str]:
|
|
217
|
-
"""Parse pushed references from git output."""
|
|
218
221
|
pushed_refs = []
|
|
219
222
|
for line in lines:
|
|
220
223
|
if line.startswith(("*", "+", "=")):
|
|
221
|
-
# Parse porcelain output: flag:from:to summary
|
|
222
224
|
parts = line.split("\t")
|
|
223
225
|
if len(parts) >= 2:
|
|
224
226
|
summary = parts[1] if len(parts) > 1 else ""
|
|
@@ -226,22 +228,18 @@ class GitService:
|
|
|
226
228
|
return pushed_refs
|
|
227
229
|
|
|
228
230
|
def _display_push_results(self, pushed_refs: list[str]) -> None:
|
|
229
|
-
"""Display the push results to console."""
|
|
230
231
|
if pushed_refs:
|
|
231
232
|
self.console.print(
|
|
232
|
-
f"[green]✅[/ green] Successfully pushed {len(pushed_refs)} ref(s) to remote:"
|
|
233
|
+
f"[green]✅[/ green] Successfully pushed {len(pushed_refs)} ref(s) to remote: "
|
|
233
234
|
)
|
|
234
235
|
for ref in pushed_refs:
|
|
235
|
-
self.console.print(f"
|
|
236
|
+
self.console.print(f" [dim]→ {ref}[/ dim]")
|
|
236
237
|
else:
|
|
237
|
-
# Get commit count as fallback
|
|
238
238
|
self._display_commit_count_push()
|
|
239
239
|
|
|
240
240
|
def _display_commit_count_push(self) -> None:
|
|
241
|
-
"""Fallback method to show commit count information."""
|
|
242
241
|
try:
|
|
243
|
-
|
|
244
|
-
result = self._run_git_command(["rev-list", "--count", "@{u}..HEAD"])
|
|
242
|
+
result = self._run_git_command(GIT_COMMANDS["commits_ahead"])
|
|
245
243
|
if result.returncode == 0 and result.stdout.strip().isdigit():
|
|
246
244
|
commit_count = int(result.stdout.strip())
|
|
247
245
|
if commit_count > 0:
|
|
@@ -253,24 +251,23 @@ class GitService:
|
|
|
253
251
|
"[green]✅[/ green] Pushed to remote (up to date)"
|
|
254
252
|
)
|
|
255
253
|
else:
|
|
256
|
-
# Even more basic fallback
|
|
257
254
|
self.console.print("[green]✅[/ green] Successfully pushed to remote")
|
|
258
255
|
except (ValueError, Exception):
|
|
259
256
|
self.console.print("[green]✅[/ green] Successfully pushed to remote")
|
|
260
257
|
|
|
261
258
|
def get_current_branch(self) -> str | None:
|
|
262
259
|
try:
|
|
263
|
-
result = self._run_git_command(["
|
|
260
|
+
result = self._run_git_command(GIT_COMMANDS["current_branch"])
|
|
264
261
|
return result.stdout.strip() if result.returncode == 0 else None
|
|
265
262
|
except (subprocess.SubprocessError, OSError, FileNotFoundError):
|
|
266
263
|
return None
|
|
267
264
|
|
|
268
|
-
def get_commit_message_suggestions(self,
|
|
269
|
-
if not
|
|
265
|
+
def get_commit_message_suggestions(self, changed_files: list[str]) -> list[str]:
|
|
266
|
+
if not changed_files:
|
|
270
267
|
return ["Update project files"]
|
|
271
|
-
file_categories = self._categorize_files(
|
|
268
|
+
file_categories = self._categorize_files(changed_files)
|
|
272
269
|
messages = self._generate_category_messages(file_categories)
|
|
273
|
-
messages.extend(self._generate_specific_messages(
|
|
270
|
+
messages.extend(self._generate_specific_messages(changed_files))
|
|
274
271
|
|
|
275
272
|
return messages[:5]
|
|
276
273
|
|
|
@@ -322,11 +319,10 @@ class GitService:
|
|
|
322
319
|
return messages
|
|
323
320
|
|
|
324
321
|
def get_unpushed_commit_count(self) -> int:
|
|
325
|
-
"""Get the number of unpushed commits."""
|
|
326
322
|
from contextlib import suppress
|
|
327
323
|
|
|
328
324
|
with suppress(ValueError, Exception):
|
|
329
|
-
result = self._run_git_command(["
|
|
325
|
+
result = self._run_git_command(GIT_COMMANDS["commits_ahead"])
|
|
330
326
|
if result.returncode == 0 and result.stdout.strip().isdigit():
|
|
331
327
|
return int(result.stdout.strip())
|
|
332
328
|
return 0
|
|
@@ -9,6 +9,7 @@ from datetime import datetime
|
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
from typing import Any
|
|
11
11
|
|
|
12
|
+
import requests
|
|
12
13
|
from rich.console import Console
|
|
13
14
|
|
|
14
15
|
from crackerjack.models.protocols import FileSystemInterface
|
|
@@ -18,7 +19,7 @@ from crackerjack.models.protocols import FileSystemInterface
|
|
|
18
19
|
class ProjectHealth:
|
|
19
20
|
lint_error_trend: list[int] = field(default_factory=list)
|
|
20
21
|
test_coverage_trend: list[float] = field(default_factory=list)
|
|
21
|
-
dependency_age: dict[str, int] = field(default_factory=dict)
|
|
22
|
+
dependency_age: dict[str, int] = field(default_factory=dict[str, t.Any])
|
|
22
23
|
config_completeness: float = 0.0
|
|
23
24
|
last_updated: float = field(default_factory=time.time)
|
|
24
25
|
|
|
@@ -344,14 +345,13 @@ class HealthMetricsService:
|
|
|
344
345
|
|
|
345
346
|
def _fetch_package_data(self, package_name: str) -> dict[str, t.Any] | None:
|
|
346
347
|
try:
|
|
347
|
-
import urllib.request
|
|
348
348
|
from urllib.parse import urlparse
|
|
349
349
|
|
|
350
|
-
url = f"https
|
|
350
|
+
url = f"https: //pypi.org/pypi/{package_name}/json"
|
|
351
351
|
|
|
352
352
|
parsed = urlparse(url)
|
|
353
353
|
if parsed.scheme != "https" or parsed.netloc != "pypi.org":
|
|
354
|
-
msg = f"Invalid URL: only https
|
|
354
|
+
msg = f"Invalid URL: only https: //pypi.org URLs are allowed, got {url}"
|
|
355
355
|
raise ValueError(msg)
|
|
356
356
|
|
|
357
357
|
if not parsed.path.startswith("/pypi/") or not parsed.path.endswith(
|
|
@@ -360,9 +360,10 @@ class HealthMetricsService:
|
|
|
360
360
|
msg = f"Invalid PyPI API path: {parsed.path}"
|
|
361
361
|
raise ValueError(msg)
|
|
362
362
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
363
|
+
response = requests.get(url, timeout=10, verify=True)
|
|
364
|
+
response.raise_for_status()
|
|
365
|
+
json_result = response.json()
|
|
366
|
+
return t.cast(dict[str, t.Any] | None, json_result)
|
|
366
367
|
except Exception:
|
|
367
368
|
return None
|
|
368
369
|
|
|
@@ -378,7 +379,8 @@ class HealthMetricsService:
|
|
|
378
379
|
if not release_info:
|
|
379
380
|
return None
|
|
380
381
|
|
|
381
|
-
|
|
382
|
+
upload_time_raw = release_info[0].get("upload_time", "")
|
|
383
|
+
return t.cast(str | None, upload_time_raw)
|
|
382
384
|
|
|
383
385
|
def _calculate_days_since_upload(self, upload_time: str) -> int | None:
|
|
384
386
|
try:
|