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.

Files changed (200) hide show
  1. crackerjack/__main__.py +1350 -34
  2. crackerjack/adapters/__init__.py +17 -0
  3. crackerjack/adapters/lsp_client.py +358 -0
  4. crackerjack/adapters/rust_tool_adapter.py +194 -0
  5. crackerjack/adapters/rust_tool_manager.py +193 -0
  6. crackerjack/adapters/skylos_adapter.py +231 -0
  7. crackerjack/adapters/zuban_adapter.py +560 -0
  8. crackerjack/agents/base.py +7 -3
  9. crackerjack/agents/coordinator.py +271 -33
  10. crackerjack/agents/documentation_agent.py +9 -15
  11. crackerjack/agents/dry_agent.py +3 -15
  12. crackerjack/agents/formatting_agent.py +1 -1
  13. crackerjack/agents/import_optimization_agent.py +36 -180
  14. crackerjack/agents/performance_agent.py +17 -98
  15. crackerjack/agents/performance_helpers.py +7 -31
  16. crackerjack/agents/proactive_agent.py +1 -3
  17. crackerjack/agents/refactoring_agent.py +16 -85
  18. crackerjack/agents/refactoring_helpers.py +7 -42
  19. crackerjack/agents/security_agent.py +9 -48
  20. crackerjack/agents/test_creation_agent.py +356 -513
  21. crackerjack/agents/test_specialist_agent.py +0 -4
  22. crackerjack/api.py +6 -25
  23. crackerjack/cli/cache_handlers.py +204 -0
  24. crackerjack/cli/cache_handlers_enhanced.py +683 -0
  25. crackerjack/cli/facade.py +100 -0
  26. crackerjack/cli/handlers.py +224 -9
  27. crackerjack/cli/interactive.py +6 -4
  28. crackerjack/cli/options.py +642 -55
  29. crackerjack/cli/utils.py +2 -1
  30. crackerjack/code_cleaner.py +58 -117
  31. crackerjack/config/global_lock_config.py +8 -48
  32. crackerjack/config/hooks.py +53 -62
  33. crackerjack/core/async_workflow_orchestrator.py +24 -34
  34. crackerjack/core/autofix_coordinator.py +3 -17
  35. crackerjack/core/enhanced_container.py +64 -6
  36. crackerjack/core/file_lifecycle.py +12 -89
  37. crackerjack/core/performance.py +2 -2
  38. crackerjack/core/performance_monitor.py +15 -55
  39. crackerjack/core/phase_coordinator.py +257 -218
  40. crackerjack/core/resource_manager.py +14 -90
  41. crackerjack/core/service_watchdog.py +62 -95
  42. crackerjack/core/session_coordinator.py +149 -0
  43. crackerjack/core/timeout_manager.py +14 -72
  44. crackerjack/core/websocket_lifecycle.py +13 -78
  45. crackerjack/core/workflow_orchestrator.py +558 -240
  46. crackerjack/docs/INDEX.md +11 -0
  47. crackerjack/docs/generated/api/API_REFERENCE.md +10895 -0
  48. crackerjack/docs/generated/api/CLI_REFERENCE.md +109 -0
  49. crackerjack/docs/generated/api/CROSS_REFERENCES.md +1755 -0
  50. crackerjack/docs/generated/api/PROTOCOLS.md +3 -0
  51. crackerjack/docs/generated/api/SERVICES.md +1252 -0
  52. crackerjack/documentation/__init__.py +31 -0
  53. crackerjack/documentation/ai_templates.py +756 -0
  54. crackerjack/documentation/dual_output_generator.py +765 -0
  55. crackerjack/documentation/mkdocs_integration.py +518 -0
  56. crackerjack/documentation/reference_generator.py +977 -0
  57. crackerjack/dynamic_config.py +55 -50
  58. crackerjack/executors/async_hook_executor.py +10 -15
  59. crackerjack/executors/cached_hook_executor.py +117 -43
  60. crackerjack/executors/hook_executor.py +8 -34
  61. crackerjack/executors/hook_lock_manager.py +26 -183
  62. crackerjack/executors/individual_hook_executor.py +13 -11
  63. crackerjack/executors/lsp_aware_hook_executor.py +270 -0
  64. crackerjack/executors/tool_proxy.py +417 -0
  65. crackerjack/hooks/lsp_hook.py +79 -0
  66. crackerjack/intelligence/adaptive_learning.py +25 -10
  67. crackerjack/intelligence/agent_orchestrator.py +2 -5
  68. crackerjack/intelligence/agent_registry.py +34 -24
  69. crackerjack/intelligence/agent_selector.py +5 -7
  70. crackerjack/interactive.py +17 -6
  71. crackerjack/managers/async_hook_manager.py +0 -1
  72. crackerjack/managers/hook_manager.py +79 -1
  73. crackerjack/managers/publish_manager.py +66 -13
  74. crackerjack/managers/test_command_builder.py +5 -17
  75. crackerjack/managers/test_executor.py +1 -3
  76. crackerjack/managers/test_manager.py +109 -7
  77. crackerjack/managers/test_manager_backup.py +10 -9
  78. crackerjack/mcp/cache.py +2 -2
  79. crackerjack/mcp/client_runner.py +1 -1
  80. crackerjack/mcp/context.py +191 -68
  81. crackerjack/mcp/dashboard.py +7 -5
  82. crackerjack/mcp/enhanced_progress_monitor.py +31 -28
  83. crackerjack/mcp/file_monitor.py +30 -23
  84. crackerjack/mcp/progress_components.py +31 -21
  85. crackerjack/mcp/progress_monitor.py +50 -53
  86. crackerjack/mcp/rate_limiter.py +6 -6
  87. crackerjack/mcp/server_core.py +161 -32
  88. crackerjack/mcp/service_watchdog.py +2 -1
  89. crackerjack/mcp/state.py +4 -7
  90. crackerjack/mcp/task_manager.py +11 -9
  91. crackerjack/mcp/tools/core_tools.py +174 -33
  92. crackerjack/mcp/tools/error_analyzer.py +3 -2
  93. crackerjack/mcp/tools/execution_tools.py +15 -12
  94. crackerjack/mcp/tools/execution_tools_backup.py +42 -30
  95. crackerjack/mcp/tools/intelligence_tool_registry.py +7 -5
  96. crackerjack/mcp/tools/intelligence_tools.py +5 -2
  97. crackerjack/mcp/tools/monitoring_tools.py +33 -70
  98. crackerjack/mcp/tools/proactive_tools.py +24 -11
  99. crackerjack/mcp/tools/progress_tools.py +5 -8
  100. crackerjack/mcp/tools/utility_tools.py +20 -14
  101. crackerjack/mcp/tools/workflow_executor.py +62 -40
  102. crackerjack/mcp/websocket/app.py +8 -0
  103. crackerjack/mcp/websocket/endpoints.py +352 -357
  104. crackerjack/mcp/websocket/jobs.py +40 -57
  105. crackerjack/mcp/websocket/monitoring_endpoints.py +2935 -0
  106. crackerjack/mcp/websocket/server.py +7 -25
  107. crackerjack/mcp/websocket/websocket_handler.py +6 -17
  108. crackerjack/mixins/__init__.py +3 -0
  109. crackerjack/mixins/error_handling.py +145 -0
  110. crackerjack/models/config.py +21 -1
  111. crackerjack/models/config_adapter.py +49 -1
  112. crackerjack/models/protocols.py +176 -107
  113. crackerjack/models/resource_protocols.py +55 -210
  114. crackerjack/models/task.py +3 -0
  115. crackerjack/monitoring/ai_agent_watchdog.py +13 -13
  116. crackerjack/monitoring/metrics_collector.py +426 -0
  117. crackerjack/monitoring/regression_prevention.py +8 -8
  118. crackerjack/monitoring/websocket_server.py +643 -0
  119. crackerjack/orchestration/advanced_orchestrator.py +11 -6
  120. crackerjack/orchestration/coverage_improvement.py +3 -3
  121. crackerjack/orchestration/execution_strategies.py +26 -6
  122. crackerjack/orchestration/test_progress_streamer.py +8 -5
  123. crackerjack/plugins/base.py +2 -2
  124. crackerjack/plugins/hooks.py +7 -0
  125. crackerjack/plugins/managers.py +11 -8
  126. crackerjack/security/__init__.py +0 -1
  127. crackerjack/security/audit.py +90 -105
  128. crackerjack/services/anomaly_detector.py +392 -0
  129. crackerjack/services/api_extractor.py +615 -0
  130. crackerjack/services/backup_service.py +2 -2
  131. crackerjack/services/bounded_status_operations.py +15 -152
  132. crackerjack/services/cache.py +127 -1
  133. crackerjack/services/changelog_automation.py +395 -0
  134. crackerjack/services/config.py +18 -11
  135. crackerjack/services/config_merge.py +30 -85
  136. crackerjack/services/config_template.py +506 -0
  137. crackerjack/services/contextual_ai_assistant.py +48 -22
  138. crackerjack/services/coverage_badge_service.py +171 -0
  139. crackerjack/services/coverage_ratchet.py +41 -17
  140. crackerjack/services/debug.py +3 -3
  141. crackerjack/services/dependency_analyzer.py +460 -0
  142. crackerjack/services/dependency_monitor.py +14 -11
  143. crackerjack/services/documentation_generator.py +491 -0
  144. crackerjack/services/documentation_service.py +675 -0
  145. crackerjack/services/enhanced_filesystem.py +6 -5
  146. crackerjack/services/enterprise_optimizer.py +865 -0
  147. crackerjack/services/error_pattern_analyzer.py +676 -0
  148. crackerjack/services/file_hasher.py +1 -1
  149. crackerjack/services/git.py +41 -45
  150. crackerjack/services/health_metrics.py +10 -8
  151. crackerjack/services/heatmap_generator.py +735 -0
  152. crackerjack/services/initialization.py +30 -33
  153. crackerjack/services/input_validator.py +5 -97
  154. crackerjack/services/intelligent_commit.py +327 -0
  155. crackerjack/services/log_manager.py +15 -12
  156. crackerjack/services/logging.py +4 -3
  157. crackerjack/services/lsp_client.py +628 -0
  158. crackerjack/services/memory_optimizer.py +409 -0
  159. crackerjack/services/metrics.py +42 -33
  160. crackerjack/services/parallel_executor.py +416 -0
  161. crackerjack/services/pattern_cache.py +1 -1
  162. crackerjack/services/pattern_detector.py +6 -6
  163. crackerjack/services/performance_benchmarks.py +250 -576
  164. crackerjack/services/performance_cache.py +382 -0
  165. crackerjack/services/performance_monitor.py +565 -0
  166. crackerjack/services/predictive_analytics.py +510 -0
  167. crackerjack/services/quality_baseline.py +234 -0
  168. crackerjack/services/quality_baseline_enhanced.py +646 -0
  169. crackerjack/services/quality_intelligence.py +785 -0
  170. crackerjack/services/regex_patterns.py +605 -524
  171. crackerjack/services/regex_utils.py +43 -123
  172. crackerjack/services/secure_path_utils.py +5 -164
  173. crackerjack/services/secure_status_formatter.py +30 -141
  174. crackerjack/services/secure_subprocess.py +11 -92
  175. crackerjack/services/security.py +61 -30
  176. crackerjack/services/security_logger.py +18 -22
  177. crackerjack/services/server_manager.py +124 -16
  178. crackerjack/services/status_authentication.py +16 -159
  179. crackerjack/services/status_security_manager.py +4 -131
  180. crackerjack/services/terminal_utils.py +0 -0
  181. crackerjack/services/thread_safe_status_collector.py +19 -125
  182. crackerjack/services/unified_config.py +21 -13
  183. crackerjack/services/validation_rate_limiter.py +5 -54
  184. crackerjack/services/version_analyzer.py +459 -0
  185. crackerjack/services/version_checker.py +1 -1
  186. crackerjack/services/websocket_resource_limiter.py +10 -144
  187. crackerjack/services/zuban_lsp_service.py +390 -0
  188. crackerjack/slash_commands/__init__.py +2 -7
  189. crackerjack/slash_commands/run.md +2 -2
  190. crackerjack/tools/validate_input_validator_patterns.py +14 -40
  191. crackerjack/tools/validate_regex_patterns.py +19 -48
  192. {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.dist-info}/METADATA +197 -26
  193. crackerjack-0.33.1.dist-info/RECORD +229 -0
  194. crackerjack/CLAUDE.md +0 -207
  195. crackerjack/RULES.md +0 -380
  196. crackerjack/py313.py +0 -234
  197. crackerjack-0.32.0.dist-info/RECORD +0 -180
  198. {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.dist-info}/WHEEL +0 -0
  199. {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.dist-info}/entry_points.txt +0 -0
  200. {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.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)