crackerjack 0.33.0__py3-none-any.whl → 0.33.2__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 +4 -13
- 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 +104 -204
- 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 +171 -174
- 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 +44 -8
- crackerjack/managers/test_command_builder.py +1 -15
- crackerjack/managers/test_executor.py +1 -3
- crackerjack/managers/test_manager.py +98 -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 +17 -16
- 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 +173 -32
- crackerjack/mcp/tools/error_analyzer.py +3 -2
- crackerjack/mcp/tools/execution_tools.py +8 -10
- 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 +0 -2
- crackerjack/mixins/error_handling.py +1 -70
- crackerjack/models/config.py +12 -1
- crackerjack/models/config_adapter.py +49 -1
- crackerjack/models/protocols.py +122 -122
- crackerjack/models/resource_protocols.py +55 -210
- 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 +6 -35
- 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 +15 -9
- crackerjack/services/config_merge.py +19 -80
- 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 +27 -25
- 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 +8 -25
- crackerjack/services/health_metrics.py +10 -8
- crackerjack/services/heatmap_generator.py +735 -0
- crackerjack/services/initialization.py +11 -30
- 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 +19 -87
- crackerjack/services/metrics.py +42 -33
- crackerjack/services/parallel_executor.py +9 -67
- crackerjack/services/pattern_cache.py +1 -1
- crackerjack/services/pattern_detector.py +6 -6
- crackerjack/services/performance_benchmarks.py +18 -59
- crackerjack/services/performance_cache.py +20 -81
- crackerjack/services/performance_monitor.py +27 -95
- 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 +618 -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 +9 -41
- crackerjack/services/security_logger.py +12 -24
- crackerjack/services/server_manager.py +124 -16
- crackerjack/services/status_authentication.py +16 -159
- crackerjack/services/status_security_manager.py +4 -131
- 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.33.0.dist-info → crackerjack-0.33.2.dist-info}/METADATA +196 -25
- crackerjack-0.33.2.dist-info/RECORD +229 -0
- crackerjack/CLAUDE.md +0 -207
- crackerjack/RULES.md +0 -380
- crackerjack/py313.py +0 -234
- crackerjack-0.33.0.dist-info/RECORD +0 -187
- {crackerjack-0.33.0.dist-info → crackerjack-0.33.2.dist-info}/WHEEL +0 -0
- {crackerjack-0.33.0.dist-info → crackerjack-0.33.2.dist-info}/entry_points.txt +0 -0
- {crackerjack-0.33.0.dist-info → crackerjack-0.33.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"""Rust tool manager for unified execution and coordination."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import time
|
|
5
|
+
import typing as t
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from .rust_tool_adapter import BaseRustToolAdapter, ToolResult
|
|
9
|
+
from .skylos_adapter import SkylosAdapter
|
|
10
|
+
from .zuban_adapter import ZubanAdapter
|
|
11
|
+
|
|
12
|
+
if t.TYPE_CHECKING:
|
|
13
|
+
from crackerjack.orchestration.execution_strategies import ExecutionContext
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class RustToolHookManager:
|
|
17
|
+
"""Manager for coordinating Rust-based analysis tools."""
|
|
18
|
+
|
|
19
|
+
def __init__(self, context: "ExecutionContext") -> None:
|
|
20
|
+
"""Initialize manager with execution context."""
|
|
21
|
+
self.context = context
|
|
22
|
+
self.adapters: dict[str, BaseRustToolAdapter] = {}
|
|
23
|
+
self._initialize_adapters()
|
|
24
|
+
|
|
25
|
+
def _initialize_adapters(self) -> None:
|
|
26
|
+
"""Initialize available Rust tool adapters."""
|
|
27
|
+
# Dead code detection with Skylos
|
|
28
|
+
self.adapters["skylos"] = SkylosAdapter(context=self.context)
|
|
29
|
+
|
|
30
|
+
# Type checking with Zuban
|
|
31
|
+
self.adapters["zuban"] = ZubanAdapter(context=self.context)
|
|
32
|
+
|
|
33
|
+
async def run_all_tools(
|
|
34
|
+
self, target_files: list[Path] | None = None
|
|
35
|
+
) -> dict[str, ToolResult]:
|
|
36
|
+
"""Run all available Rust tools in parallel."""
|
|
37
|
+
target_files = target_files or []
|
|
38
|
+
|
|
39
|
+
# Filter to available tools only
|
|
40
|
+
available_adapters = {
|
|
41
|
+
name: adapter
|
|
42
|
+
for name, adapter in self.adapters.items()
|
|
43
|
+
if adapter.validate_tool_available()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if not available_adapters:
|
|
47
|
+
return {
|
|
48
|
+
"error": ToolResult(
|
|
49
|
+
success=False,
|
|
50
|
+
error=(
|
|
51
|
+
"No Rust tools are available. "
|
|
52
|
+
"Install skylos and zuban with: uv add skylos zuban"
|
|
53
|
+
),
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
# Run tools in parallel
|
|
58
|
+
tasks = [
|
|
59
|
+
self._run_single_tool(name, adapter, target_files)
|
|
60
|
+
for name, adapter in available_adapters.items()
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
results = await asyncio.gather(*tasks, return_exceptions=True)
|
|
64
|
+
|
|
65
|
+
# Process results
|
|
66
|
+
tool_results = {}
|
|
67
|
+
for i, (name, _) in enumerate(available_adapters.items()):
|
|
68
|
+
result = results[i]
|
|
69
|
+
if isinstance(result, Exception):
|
|
70
|
+
tool_results[name] = ToolResult(
|
|
71
|
+
success=False,
|
|
72
|
+
error=f"Tool execution failed: {result}",
|
|
73
|
+
)
|
|
74
|
+
elif isinstance(result, ToolResult): # Explicit check for mypy
|
|
75
|
+
tool_results[name] = result
|
|
76
|
+
|
|
77
|
+
return tool_results
|
|
78
|
+
|
|
79
|
+
async def run_single_tool(
|
|
80
|
+
self, tool_name: str, target_files: list[Path] | None = None
|
|
81
|
+
) -> ToolResult:
|
|
82
|
+
"""Run a single Rust tool by name."""
|
|
83
|
+
if tool_name not in self.adapters:
|
|
84
|
+
return ToolResult(
|
|
85
|
+
success=False,
|
|
86
|
+
error=(
|
|
87
|
+
f"Unknown tool: {tool_name}. "
|
|
88
|
+
f"Available: {list[t.Any](self.adapters.keys())}"
|
|
89
|
+
),
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
adapter = self.adapters[tool_name]
|
|
93
|
+
if not adapter.validate_tool_available():
|
|
94
|
+
return ToolResult(
|
|
95
|
+
success=False,
|
|
96
|
+
error=(
|
|
97
|
+
f"Tool {tool_name} is not available. "
|
|
98
|
+
f"Install with: uv add {tool_name}"
|
|
99
|
+
),
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
return await self._run_single_tool(tool_name, adapter, target_files or [])
|
|
103
|
+
|
|
104
|
+
async def _run_single_tool(
|
|
105
|
+
self, name: str, adapter: BaseRustToolAdapter, target_files: list[Path]
|
|
106
|
+
) -> ToolResult:
|
|
107
|
+
"""Execute a single tool adapter."""
|
|
108
|
+
start_time = time.time()
|
|
109
|
+
|
|
110
|
+
try:
|
|
111
|
+
# Get command arguments
|
|
112
|
+
cmd_args = adapter.get_command_args(target_files)
|
|
113
|
+
|
|
114
|
+
# Execute command
|
|
115
|
+
process = await asyncio.create_subprocess_exec(
|
|
116
|
+
*cmd_args,
|
|
117
|
+
stdout=asyncio.subprocess.PIPE,
|
|
118
|
+
stderr=asyncio.subprocess.STDOUT,
|
|
119
|
+
cwd=self.context.working_directory,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
stdout, _ = await process.communicate()
|
|
123
|
+
output = stdout.decode() if stdout else ""
|
|
124
|
+
|
|
125
|
+
# Parse output
|
|
126
|
+
result = adapter.parse_output(output)
|
|
127
|
+
result.execution_time = time.time() - start_time
|
|
128
|
+
|
|
129
|
+
return result
|
|
130
|
+
|
|
131
|
+
except Exception as e:
|
|
132
|
+
return ToolResult(
|
|
133
|
+
success=False,
|
|
134
|
+
error=f"Failed to execute {name}: {e}",
|
|
135
|
+
execution_time=time.time() - start_time,
|
|
136
|
+
tool_version=adapter.get_tool_version(),
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
def get_available_tools(self) -> list[str]:
|
|
140
|
+
"""Get list[t.Any] of available Rust tools."""
|
|
141
|
+
return [
|
|
142
|
+
name
|
|
143
|
+
for name, adapter in self.adapters.items()
|
|
144
|
+
if adapter.validate_tool_available()
|
|
145
|
+
]
|
|
146
|
+
|
|
147
|
+
def get_tool_info(self) -> dict[str, dict[str, t.Any]]:
|
|
148
|
+
"""Get information about all configured tools."""
|
|
149
|
+
info: dict[str, dict[str, t.Any]] = {}
|
|
150
|
+
for name, adapter in self.adapters.items():
|
|
151
|
+
info[name] = {
|
|
152
|
+
"available": adapter.validate_tool_available(),
|
|
153
|
+
"supports_json": adapter.supports_json_output(),
|
|
154
|
+
"version": adapter.get_tool_version(),
|
|
155
|
+
"tool_name": adapter.get_tool_name(),
|
|
156
|
+
}
|
|
157
|
+
return info
|
|
158
|
+
|
|
159
|
+
def create_consolidated_report(
|
|
160
|
+
self, results: dict[str, ToolResult]
|
|
161
|
+
) -> dict[str, t.Any]:
|
|
162
|
+
"""Create a consolidated report from multiple tool results."""
|
|
163
|
+
total_issues = 0
|
|
164
|
+
total_errors = 0
|
|
165
|
+
total_warnings = 0
|
|
166
|
+
all_success = True
|
|
167
|
+
execution_times = {}
|
|
168
|
+
|
|
169
|
+
for tool_name, result in results.items():
|
|
170
|
+
if tool_name == "error":
|
|
171
|
+
all_success = False
|
|
172
|
+
continue
|
|
173
|
+
|
|
174
|
+
total_issues += len(result.issues)
|
|
175
|
+
total_errors += result.error_count
|
|
176
|
+
total_warnings += result.warning_count
|
|
177
|
+
execution_times[tool_name] = result.execution_time
|
|
178
|
+
|
|
179
|
+
if not result.success:
|
|
180
|
+
all_success = False
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
"overall_success": all_success,
|
|
184
|
+
"total_issues": total_issues,
|
|
185
|
+
"total_errors": total_errors,
|
|
186
|
+
"total_warnings": total_warnings,
|
|
187
|
+
"tools_run": list[t.Any](results.keys()),
|
|
188
|
+
"execution_times": execution_times,
|
|
189
|
+
"total_time": sum(execution_times.values()),
|
|
190
|
+
"results_by_tool": {
|
|
191
|
+
name: result.to_dict() for name, result in results.items()
|
|
192
|
+
},
|
|
193
|
+
}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
"""Skylos adapter for dead code detection."""
|
|
2
|
+
|
|
3
|
+
import typing as t
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from .rust_tool_adapter import BaseRustToolAdapter, Issue, ToolResult
|
|
8
|
+
|
|
9
|
+
if t.TYPE_CHECKING:
|
|
10
|
+
from crackerjack.orchestration.execution_strategies import ExecutionContext
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class DeadCodeIssue(Issue):
|
|
15
|
+
"""Skylos dead code detection issue."""
|
|
16
|
+
|
|
17
|
+
severity: str = "warning" # Override default, dead code is typically warning
|
|
18
|
+
issue_type: str = "unknown" # "import", "function", "class", "variable", etc.
|
|
19
|
+
name: str = "unknown"
|
|
20
|
+
confidence: float = 0.0
|
|
21
|
+
|
|
22
|
+
def to_dict(self) -> dict[str, t.Any]:
|
|
23
|
+
"""Convert issue to dictionary with Skylos-specific fields."""
|
|
24
|
+
base_dict = super().to_dict()
|
|
25
|
+
base_dict.update(
|
|
26
|
+
{
|
|
27
|
+
"issue_type": self.issue_type,
|
|
28
|
+
"name": self.name,
|
|
29
|
+
"confidence": self.confidence,
|
|
30
|
+
}
|
|
31
|
+
)
|
|
32
|
+
return base_dict
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class SkylosAdapter(BaseRustToolAdapter):
|
|
36
|
+
"""Skylos dead code detection adapter."""
|
|
37
|
+
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
context: "ExecutionContext",
|
|
41
|
+
confidence_threshold: int = 86,
|
|
42
|
+
web_dashboard_port: int = 5090,
|
|
43
|
+
) -> None:
|
|
44
|
+
"""Initialize Skylos adapter."""
|
|
45
|
+
super().__init__(context)
|
|
46
|
+
self.confidence_threshold = confidence_threshold
|
|
47
|
+
self.web_dashboard_port = web_dashboard_port
|
|
48
|
+
|
|
49
|
+
def get_tool_name(self) -> str:
|
|
50
|
+
"""Get the name of the tool."""
|
|
51
|
+
return "skylos"
|
|
52
|
+
|
|
53
|
+
def supports_json_output(self) -> bool:
|
|
54
|
+
"""Skylos supports JSON output mode."""
|
|
55
|
+
return True
|
|
56
|
+
|
|
57
|
+
def get_command_args(self, target_files: list[Path]) -> list[str]:
|
|
58
|
+
"""Get command arguments for Skylos execution."""
|
|
59
|
+
args = ["uv", "run", "skylos", "--confidence", str(self.confidence_threshold)]
|
|
60
|
+
|
|
61
|
+
# Add JSON mode for AI agents
|
|
62
|
+
if self._should_use_json_output():
|
|
63
|
+
args.append("--json")
|
|
64
|
+
|
|
65
|
+
# Add web dashboard for interactive mode
|
|
66
|
+
if self.context.interactive:
|
|
67
|
+
args.extend(["--web", "--port", str(self.web_dashboard_port)])
|
|
68
|
+
|
|
69
|
+
# Add target files or default to current directory
|
|
70
|
+
if target_files:
|
|
71
|
+
args.extend(str(f) for f in target_files)
|
|
72
|
+
else:
|
|
73
|
+
args.append(".")
|
|
74
|
+
|
|
75
|
+
return args
|
|
76
|
+
|
|
77
|
+
def parse_output(self, output: str) -> ToolResult:
|
|
78
|
+
"""Parse Skylos output into standardized result."""
|
|
79
|
+
if self._should_use_json_output():
|
|
80
|
+
return self._parse_json_output(output)
|
|
81
|
+
return self._parse_text_output(output)
|
|
82
|
+
|
|
83
|
+
def _parse_json_output(self, output: str) -> ToolResult:
|
|
84
|
+
"""Parse JSON output for AI agents."""
|
|
85
|
+
data = self._parse_json_output_safe(output)
|
|
86
|
+
if data is None:
|
|
87
|
+
return self._create_error_result(
|
|
88
|
+
"Invalid JSON output from Skylos", raw_output=output
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
try:
|
|
92
|
+
issues: list[Issue] = [
|
|
93
|
+
DeadCodeIssue(
|
|
94
|
+
file_path=Path(item["file"]),
|
|
95
|
+
line_number=item.get("line", 1),
|
|
96
|
+
message=f"Dead {item['type']}: {item['name']}",
|
|
97
|
+
severity="warning", # Dead code is typically a warning, not error
|
|
98
|
+
issue_type=item["type"],
|
|
99
|
+
name=item["name"],
|
|
100
|
+
confidence=item.get("confidence", 0.0),
|
|
101
|
+
)
|
|
102
|
+
for item in data.get("dead_code", [])
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
# Skylos success means no issues found
|
|
106
|
+
success = len(issues) == 0
|
|
107
|
+
|
|
108
|
+
return ToolResult(
|
|
109
|
+
success=success,
|
|
110
|
+
issues=issues,
|
|
111
|
+
raw_output=output,
|
|
112
|
+
tool_version=self.get_tool_version(),
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
except (KeyError, TypeError, ValueError) as e:
|
|
116
|
+
return self._create_error_result(
|
|
117
|
+
f"Failed to parse Skylos JSON output: {e}", raw_output=output
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
def _parse_text_output(self, output: str) -> ToolResult:
|
|
121
|
+
"""Parse text output for human-readable display."""
|
|
122
|
+
issues: list[Issue] = []
|
|
123
|
+
|
|
124
|
+
if not output.strip():
|
|
125
|
+
# No output typically means no dead code found
|
|
126
|
+
return ToolResult(
|
|
127
|
+
success=True,
|
|
128
|
+
issues=[],
|
|
129
|
+
raw_output=output,
|
|
130
|
+
tool_version=self.get_tool_version(),
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
# Parse Skylos text output
|
|
134
|
+
# Expected format: "file.py:line: unused import 'name' (confidence: 86%)"
|
|
135
|
+
for line in output.strip().split("\\n"):
|
|
136
|
+
line = line.strip()
|
|
137
|
+
if not line:
|
|
138
|
+
continue
|
|
139
|
+
|
|
140
|
+
issue = self._parse_text_line(line)
|
|
141
|
+
if issue:
|
|
142
|
+
issues.append(issue)
|
|
143
|
+
|
|
144
|
+
# Success if no issues found
|
|
145
|
+
success = len(issues) == 0
|
|
146
|
+
|
|
147
|
+
return ToolResult(
|
|
148
|
+
success=success,
|
|
149
|
+
issues=issues,
|
|
150
|
+
raw_output=output,
|
|
151
|
+
tool_version=self.get_tool_version(),
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
def _parse_text_line(self, line: str) -> DeadCodeIssue | None:
|
|
155
|
+
"""Parse a single line of Skylos text output."""
|
|
156
|
+
try:
|
|
157
|
+
basic_info = self._extract_basic_line_info(line)
|
|
158
|
+
if not basic_info:
|
|
159
|
+
return None
|
|
160
|
+
|
|
161
|
+
file_path, line_number, message_part = basic_info
|
|
162
|
+
issue_details = self._extract_issue_details(message_part)
|
|
163
|
+
confidence = self._extract_confidence(message_part)
|
|
164
|
+
|
|
165
|
+
return DeadCodeIssue(
|
|
166
|
+
file_path=file_path,
|
|
167
|
+
line_number=line_number,
|
|
168
|
+
message=f"Dead {issue_details['type']}: {issue_details['name']}",
|
|
169
|
+
severity="warning",
|
|
170
|
+
issue_type=issue_details["type"],
|
|
171
|
+
name=issue_details["name"],
|
|
172
|
+
confidence=confidence,
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
except (IndexError, ValueError):
|
|
176
|
+
return None
|
|
177
|
+
|
|
178
|
+
def _extract_basic_line_info(self, line: str) -> tuple[Path, int, str] | None:
|
|
179
|
+
"""Extract file path, line number, and message from line."""
|
|
180
|
+
if ":" not in line:
|
|
181
|
+
return None
|
|
182
|
+
|
|
183
|
+
parts = line.split(":", 2)
|
|
184
|
+
if len(parts) < 3:
|
|
185
|
+
return None
|
|
186
|
+
|
|
187
|
+
file_path = Path(parts[0].strip())
|
|
188
|
+
try:
|
|
189
|
+
line_number = int(parts[1].strip())
|
|
190
|
+
except ValueError:
|
|
191
|
+
line_number = 1
|
|
192
|
+
|
|
193
|
+
message_part = parts[2].strip()
|
|
194
|
+
return file_path, line_number, message_part
|
|
195
|
+
|
|
196
|
+
def _extract_issue_details(self, message_part: str) -> dict[str, str]:
|
|
197
|
+
"""Extract issue type and name from message."""
|
|
198
|
+
issue_type = "unknown"
|
|
199
|
+
name = "unknown"
|
|
200
|
+
|
|
201
|
+
lower_message = message_part.lower()
|
|
202
|
+
|
|
203
|
+
if "unused import" in lower_message:
|
|
204
|
+
issue_type = "import"
|
|
205
|
+
name = self._extract_name_from_quotes(message_part)
|
|
206
|
+
elif "unused function" in lower_message:
|
|
207
|
+
issue_type = "function"
|
|
208
|
+
name = self._extract_name_from_quotes(message_part)
|
|
209
|
+
elif "unused class" in lower_message:
|
|
210
|
+
issue_type = "class"
|
|
211
|
+
name = self._extract_name_from_quotes(message_part)
|
|
212
|
+
|
|
213
|
+
return {"type": issue_type, "name": name}
|
|
214
|
+
|
|
215
|
+
def _extract_name_from_quotes(self, message_part: str) -> str:
|
|
216
|
+
"""Extract name from single quotes in message."""
|
|
217
|
+
quoted_parts = message_part.split("'")
|
|
218
|
+
if len(quoted_parts) >= 2:
|
|
219
|
+
return quoted_parts[1]
|
|
220
|
+
return "unknown"
|
|
221
|
+
|
|
222
|
+
def _extract_confidence(self, message_part: str) -> float:
|
|
223
|
+
"""Extract confidence percentage from message."""
|
|
224
|
+
if "(confidence:" not in message_part:
|
|
225
|
+
return float(self.confidence_threshold)
|
|
226
|
+
|
|
227
|
+
try:
|
|
228
|
+
conf_part = message_part.split("(confidence:")[1].split(")")[0]
|
|
229
|
+
return float(conf_part.strip().replace("%", ""))
|
|
230
|
+
except (ValueError, IndexError):
|
|
231
|
+
return float(self.confidence_threshold)
|