superqode 0.1.5__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.
Files changed (288) hide show
  1. superqode/__init__.py +33 -0
  2. superqode/acp/__init__.py +23 -0
  3. superqode/acp/client.py +913 -0
  4. superqode/acp/permission_screen.py +457 -0
  5. superqode/acp/types.py +480 -0
  6. superqode/acp_discovery.py +856 -0
  7. superqode/agent/__init__.py +22 -0
  8. superqode/agent/edit_strategies.py +334 -0
  9. superqode/agent/loop.py +892 -0
  10. superqode/agent/qe_report_templates.py +39 -0
  11. superqode/agent/system_prompts.py +353 -0
  12. superqode/agent_output.py +721 -0
  13. superqode/agent_stream.py +953 -0
  14. superqode/agents/__init__.py +59 -0
  15. superqode/agents/acp_registry.py +305 -0
  16. superqode/agents/client.py +249 -0
  17. superqode/agents/data/augmentcode.com.toml +51 -0
  18. superqode/agents/data/cagent.dev.toml +51 -0
  19. superqode/agents/data/claude.com.toml +60 -0
  20. superqode/agents/data/codeassistant.dev.toml +51 -0
  21. superqode/agents/data/codex.openai.com.toml +57 -0
  22. superqode/agents/data/fastagent.ai.toml +66 -0
  23. superqode/agents/data/geminicli.com.toml +77 -0
  24. superqode/agents/data/goose.block.xyz.toml +54 -0
  25. superqode/agents/data/junie.jetbrains.com.toml +56 -0
  26. superqode/agents/data/kimi.moonshot.cn.toml +57 -0
  27. superqode/agents/data/llmlingagent.dev.toml +51 -0
  28. superqode/agents/data/molt.bot.toml +49 -0
  29. superqode/agents/data/opencode.ai.toml +60 -0
  30. superqode/agents/data/stakpak.dev.toml +51 -0
  31. superqode/agents/data/vtcode.dev.toml +51 -0
  32. superqode/agents/discovery.py +266 -0
  33. superqode/agents/messaging.py +160 -0
  34. superqode/agents/persona.py +166 -0
  35. superqode/agents/registry.py +421 -0
  36. superqode/agents/schema.py +72 -0
  37. superqode/agents/unified.py +367 -0
  38. superqode/app/__init__.py +111 -0
  39. superqode/app/constants.py +314 -0
  40. superqode/app/css.py +366 -0
  41. superqode/app/models.py +118 -0
  42. superqode/app/suggester.py +125 -0
  43. superqode/app/widgets.py +1591 -0
  44. superqode/app_enhanced.py +399 -0
  45. superqode/app_main.py +17187 -0
  46. superqode/approval.py +312 -0
  47. superqode/atomic.py +296 -0
  48. superqode/commands/__init__.py +1 -0
  49. superqode/commands/acp.py +965 -0
  50. superqode/commands/agents.py +180 -0
  51. superqode/commands/auth.py +278 -0
  52. superqode/commands/config.py +374 -0
  53. superqode/commands/init.py +826 -0
  54. superqode/commands/providers.py +819 -0
  55. superqode/commands/qe.py +1145 -0
  56. superqode/commands/roles.py +380 -0
  57. superqode/commands/serve.py +172 -0
  58. superqode/commands/suggestions.py +127 -0
  59. superqode/commands/superqe.py +460 -0
  60. superqode/config/__init__.py +51 -0
  61. superqode/config/loader.py +812 -0
  62. superqode/config/schema.py +498 -0
  63. superqode/core/__init__.py +111 -0
  64. superqode/core/roles.py +281 -0
  65. superqode/danger.py +386 -0
  66. superqode/data/superqode-template.yaml +1522 -0
  67. superqode/design_system.py +1080 -0
  68. superqode/dialogs/__init__.py +6 -0
  69. superqode/dialogs/base.py +39 -0
  70. superqode/dialogs/model.py +130 -0
  71. superqode/dialogs/provider.py +870 -0
  72. superqode/diff_view.py +919 -0
  73. superqode/enterprise.py +21 -0
  74. superqode/evaluation/__init__.py +25 -0
  75. superqode/evaluation/adapters.py +93 -0
  76. superqode/evaluation/behaviors.py +89 -0
  77. superqode/evaluation/engine.py +209 -0
  78. superqode/evaluation/scenarios.py +96 -0
  79. superqode/execution/__init__.py +36 -0
  80. superqode/execution/linter.py +538 -0
  81. superqode/execution/modes.py +347 -0
  82. superqode/execution/resolver.py +283 -0
  83. superqode/execution/runner.py +642 -0
  84. superqode/file_explorer.py +811 -0
  85. superqode/file_viewer.py +471 -0
  86. superqode/flash.py +183 -0
  87. superqode/guidance/__init__.py +58 -0
  88. superqode/guidance/config.py +203 -0
  89. superqode/guidance/prompts.py +71 -0
  90. superqode/harness/__init__.py +54 -0
  91. superqode/harness/accelerator.py +291 -0
  92. superqode/harness/config.py +319 -0
  93. superqode/harness/validator.py +147 -0
  94. superqode/history.py +279 -0
  95. superqode/integrations/superopt_runner.py +124 -0
  96. superqode/logging/__init__.py +49 -0
  97. superqode/logging/adapters.py +219 -0
  98. superqode/logging/formatter.py +923 -0
  99. superqode/logging/integration.py +341 -0
  100. superqode/logging/sinks.py +170 -0
  101. superqode/logging/unified_log.py +417 -0
  102. superqode/lsp/__init__.py +26 -0
  103. superqode/lsp/client.py +544 -0
  104. superqode/main.py +1069 -0
  105. superqode/mcp/__init__.py +89 -0
  106. superqode/mcp/auth_storage.py +380 -0
  107. superqode/mcp/client.py +1236 -0
  108. superqode/mcp/config.py +319 -0
  109. superqode/mcp/integration.py +337 -0
  110. superqode/mcp/oauth.py +436 -0
  111. superqode/mcp/oauth_callback.py +385 -0
  112. superqode/mcp/types.py +290 -0
  113. superqode/memory/__init__.py +31 -0
  114. superqode/memory/feedback.py +342 -0
  115. superqode/memory/store.py +522 -0
  116. superqode/notifications.py +369 -0
  117. superqode/optimization/__init__.py +5 -0
  118. superqode/optimization/config.py +33 -0
  119. superqode/permissions/__init__.py +25 -0
  120. superqode/permissions/rules.py +488 -0
  121. superqode/plan.py +323 -0
  122. superqode/providers/__init__.py +33 -0
  123. superqode/providers/gateway/__init__.py +165 -0
  124. superqode/providers/gateway/base.py +228 -0
  125. superqode/providers/gateway/litellm_gateway.py +1170 -0
  126. superqode/providers/gateway/openresponses_gateway.py +436 -0
  127. superqode/providers/health.py +297 -0
  128. superqode/providers/huggingface/__init__.py +74 -0
  129. superqode/providers/huggingface/downloader.py +472 -0
  130. superqode/providers/huggingface/endpoints.py +442 -0
  131. superqode/providers/huggingface/hub.py +531 -0
  132. superqode/providers/huggingface/inference.py +394 -0
  133. superqode/providers/huggingface/transformers_runner.py +516 -0
  134. superqode/providers/local/__init__.py +100 -0
  135. superqode/providers/local/base.py +438 -0
  136. superqode/providers/local/discovery.py +418 -0
  137. superqode/providers/local/lmstudio.py +256 -0
  138. superqode/providers/local/mlx.py +457 -0
  139. superqode/providers/local/ollama.py +486 -0
  140. superqode/providers/local/sglang.py +268 -0
  141. superqode/providers/local/tgi.py +260 -0
  142. superqode/providers/local/tool_support.py +477 -0
  143. superqode/providers/local/vllm.py +258 -0
  144. superqode/providers/manager.py +1338 -0
  145. superqode/providers/models.py +1016 -0
  146. superqode/providers/models_dev.py +578 -0
  147. superqode/providers/openresponses/__init__.py +87 -0
  148. superqode/providers/openresponses/converters/__init__.py +17 -0
  149. superqode/providers/openresponses/converters/messages.py +343 -0
  150. superqode/providers/openresponses/converters/tools.py +268 -0
  151. superqode/providers/openresponses/schema/__init__.py +56 -0
  152. superqode/providers/openresponses/schema/models.py +585 -0
  153. superqode/providers/openresponses/streaming/__init__.py +5 -0
  154. superqode/providers/openresponses/streaming/parser.py +338 -0
  155. superqode/providers/openresponses/tools/__init__.py +21 -0
  156. superqode/providers/openresponses/tools/apply_patch.py +352 -0
  157. superqode/providers/openresponses/tools/code_interpreter.py +290 -0
  158. superqode/providers/openresponses/tools/file_search.py +333 -0
  159. superqode/providers/openresponses/tools/mcp_adapter.py +252 -0
  160. superqode/providers/registry.py +716 -0
  161. superqode/providers/usage.py +332 -0
  162. superqode/pure_mode.py +384 -0
  163. superqode/qr/__init__.py +23 -0
  164. superqode/qr/dashboard.py +781 -0
  165. superqode/qr/generator.py +1018 -0
  166. superqode/qr/templates.py +135 -0
  167. superqode/safety/__init__.py +41 -0
  168. superqode/safety/sandbox.py +413 -0
  169. superqode/safety/warnings.py +256 -0
  170. superqode/server/__init__.py +33 -0
  171. superqode/server/lsp_server.py +775 -0
  172. superqode/server/web.py +250 -0
  173. superqode/session/__init__.py +25 -0
  174. superqode/session/persistence.py +580 -0
  175. superqode/session/sharing.py +477 -0
  176. superqode/session.py +475 -0
  177. superqode/sidebar.py +2991 -0
  178. superqode/stream_view.py +648 -0
  179. superqode/styles/__init__.py +3 -0
  180. superqode/superqe/__init__.py +184 -0
  181. superqode/superqe/acp_runner.py +1064 -0
  182. superqode/superqe/constitution/__init__.py +62 -0
  183. superqode/superqe/constitution/evaluator.py +308 -0
  184. superqode/superqe/constitution/loader.py +432 -0
  185. superqode/superqe/constitution/schema.py +250 -0
  186. superqode/superqe/events.py +591 -0
  187. superqode/superqe/frameworks/__init__.py +65 -0
  188. superqode/superqe/frameworks/base.py +234 -0
  189. superqode/superqe/frameworks/e2e.py +263 -0
  190. superqode/superqe/frameworks/executor.py +237 -0
  191. superqode/superqe/frameworks/javascript.py +409 -0
  192. superqode/superqe/frameworks/python.py +373 -0
  193. superqode/superqe/frameworks/registry.py +92 -0
  194. superqode/superqe/mcp_tools/__init__.py +47 -0
  195. superqode/superqe/mcp_tools/core_tools.py +418 -0
  196. superqode/superqe/mcp_tools/registry.py +230 -0
  197. superqode/superqe/mcp_tools/testing_tools.py +167 -0
  198. superqode/superqe/noise.py +89 -0
  199. superqode/superqe/orchestrator.py +778 -0
  200. superqode/superqe/roles.py +609 -0
  201. superqode/superqe/session.py +713 -0
  202. superqode/superqe/skills/__init__.py +57 -0
  203. superqode/superqe/skills/base.py +106 -0
  204. superqode/superqe/skills/core_skills.py +899 -0
  205. superqode/superqe/skills/registry.py +90 -0
  206. superqode/superqe/verifier.py +101 -0
  207. superqode/superqe_cli.py +76 -0
  208. superqode/tool_call.py +358 -0
  209. superqode/tools/__init__.py +93 -0
  210. superqode/tools/agent_tools.py +496 -0
  211. superqode/tools/base.py +324 -0
  212. superqode/tools/batch_tool.py +133 -0
  213. superqode/tools/diagnostics.py +311 -0
  214. superqode/tools/edit_tools.py +653 -0
  215. superqode/tools/enhanced_base.py +515 -0
  216. superqode/tools/file_tools.py +269 -0
  217. superqode/tools/file_tracking.py +45 -0
  218. superqode/tools/lsp_tools.py +610 -0
  219. superqode/tools/network_tools.py +350 -0
  220. superqode/tools/permissions.py +400 -0
  221. superqode/tools/question_tool.py +324 -0
  222. superqode/tools/search_tools.py +598 -0
  223. superqode/tools/shell_tools.py +259 -0
  224. superqode/tools/todo_tools.py +121 -0
  225. superqode/tools/validation.py +80 -0
  226. superqode/tools/web_tools.py +639 -0
  227. superqode/tui.py +1152 -0
  228. superqode/tui_integration.py +875 -0
  229. superqode/tui_widgets/__init__.py +27 -0
  230. superqode/tui_widgets/widgets/__init__.py +18 -0
  231. superqode/tui_widgets/widgets/progress.py +185 -0
  232. superqode/tui_widgets/widgets/tool_display.py +188 -0
  233. superqode/undo_manager.py +574 -0
  234. superqode/utils/__init__.py +5 -0
  235. superqode/utils/error_handling.py +323 -0
  236. superqode/utils/fuzzy.py +257 -0
  237. superqode/widgets/__init__.py +477 -0
  238. superqode/widgets/agent_collab.py +390 -0
  239. superqode/widgets/agent_store.py +936 -0
  240. superqode/widgets/agent_switcher.py +395 -0
  241. superqode/widgets/animation_manager.py +284 -0
  242. superqode/widgets/code_context.py +356 -0
  243. superqode/widgets/command_palette.py +412 -0
  244. superqode/widgets/connection_status.py +537 -0
  245. superqode/widgets/conversation_history.py +470 -0
  246. superqode/widgets/diff_indicator.py +155 -0
  247. superqode/widgets/enhanced_status_bar.py +385 -0
  248. superqode/widgets/enhanced_toast.py +476 -0
  249. superqode/widgets/file_browser.py +809 -0
  250. superqode/widgets/file_reference.py +585 -0
  251. superqode/widgets/issue_timeline.py +340 -0
  252. superqode/widgets/leader_key.py +264 -0
  253. superqode/widgets/mode_switcher.py +445 -0
  254. superqode/widgets/model_picker.py +234 -0
  255. superqode/widgets/permission_preview.py +1205 -0
  256. superqode/widgets/prompt.py +358 -0
  257. superqode/widgets/provider_connect.py +725 -0
  258. superqode/widgets/pty_shell.py +587 -0
  259. superqode/widgets/qe_dashboard.py +321 -0
  260. superqode/widgets/resizable_sidebar.py +377 -0
  261. superqode/widgets/response_changes.py +218 -0
  262. superqode/widgets/response_display.py +528 -0
  263. superqode/widgets/rich_tool_display.py +613 -0
  264. superqode/widgets/sidebar_panels.py +1180 -0
  265. superqode/widgets/slash_complete.py +356 -0
  266. superqode/widgets/split_view.py +612 -0
  267. superqode/widgets/status_bar.py +273 -0
  268. superqode/widgets/superqode_display.py +786 -0
  269. superqode/widgets/thinking_display.py +815 -0
  270. superqode/widgets/throbber.py +87 -0
  271. superqode/widgets/toast.py +206 -0
  272. superqode/widgets/unified_output.py +1073 -0
  273. superqode/workspace/__init__.py +75 -0
  274. superqode/workspace/artifacts.py +472 -0
  275. superqode/workspace/coordinator.py +353 -0
  276. superqode/workspace/diff_tracker.py +429 -0
  277. superqode/workspace/git_guard.py +373 -0
  278. superqode/workspace/git_snapshot.py +526 -0
  279. superqode/workspace/manager.py +750 -0
  280. superqode/workspace/snapshot.py +357 -0
  281. superqode/workspace/watcher.py +535 -0
  282. superqode/workspace/worktree.py +440 -0
  283. superqode-0.1.5.dist-info/METADATA +204 -0
  284. superqode-0.1.5.dist-info/RECORD +288 -0
  285. superqode-0.1.5.dist-info/WHEEL +5 -0
  286. superqode-0.1.5.dist-info/entry_points.txt +3 -0
  287. superqode-0.1.5.dist-info/licenses/LICENSE +648 -0
  288. superqode-0.1.5.dist-info/top_level.txt +1 -0
@@ -0,0 +1,311 @@
1
+ """
2
+ Diagnostics Tool - Expose LSP errors as a tool.
3
+
4
+ Provides code diagnostics (errors, warnings, hints) for files
5
+ by leveraging Language Server Protocol integration.
6
+
7
+ Features:
8
+ - Get errors/warnings for specific files
9
+ - Filter by severity
10
+ - Support file patterns
11
+ - Run linters directly if LSP unavailable
12
+ """
13
+
14
+ import asyncio
15
+ import shutil
16
+ import subprocess
17
+ from pathlib import Path
18
+ from typing import Any, Dict, List, Optional
19
+ from enum import Enum
20
+
21
+ from .base import Tool, ToolResult, ToolContext
22
+
23
+
24
+ class Severity(Enum):
25
+ """Diagnostic severity levels."""
26
+
27
+ ERROR = "error"
28
+ WARNING = "warning"
29
+ INFO = "info"
30
+ HINT = "hint"
31
+
32
+
33
+ class DiagnosticsTool(Tool):
34
+ """
35
+ Get code diagnostics (errors, warnings) for files.
36
+
37
+ Uses LSP when available, falls back to running linters directly.
38
+
39
+ Supports:
40
+ - Python: pyright, ruff, mypy
41
+ - TypeScript/JavaScript: tsc, eslint
42
+ - Go: go vet
43
+ - Rust: cargo check
44
+ """
45
+
46
+ # Linter commands by language
47
+ LINTERS = {
48
+ "python": [
49
+ ("ruff", ["ruff", "check", "--output-format=text"]),
50
+ ("pyright", ["pyright", "--outputjson"]),
51
+ ],
52
+ "typescript": [
53
+ ("tsc", ["tsc", "--noEmit", "--pretty", "false"]),
54
+ ("eslint", ["eslint", "--format", "unix"]),
55
+ ],
56
+ "javascript": [
57
+ ("eslint", ["eslint", "--format", "unix"]),
58
+ ],
59
+ "go": [
60
+ ("go", ["go", "vet"]),
61
+ ],
62
+ "rust": [
63
+ ("cargo", ["cargo", "check", "--message-format=short"]),
64
+ ],
65
+ }
66
+
67
+ # File extensions to language
68
+ EXTENSIONS = {
69
+ ".py": "python",
70
+ ".pyi": "python",
71
+ ".ts": "typescript",
72
+ ".tsx": "typescript",
73
+ ".js": "javascript",
74
+ ".jsx": "javascript",
75
+ ".go": "go",
76
+ ".rs": "rust",
77
+ }
78
+
79
+ @property
80
+ def name(self) -> str:
81
+ return "diagnostics"
82
+
83
+ @property
84
+ def description(self) -> str:
85
+ return "Get code diagnostics (errors, warnings) for files. Runs linters and type checkers."
86
+
87
+ @property
88
+ def parameters(self) -> Dict[str, Any]:
89
+ return {
90
+ "type": "object",
91
+ "properties": {
92
+ "path": {"type": "string", "description": "File or directory to check"},
93
+ "severity": {
94
+ "type": "string",
95
+ "enum": ["error", "warning", "info", "all"],
96
+ "description": "Minimum severity to report (default: error)",
97
+ },
98
+ "linter": {
99
+ "type": "string",
100
+ "description": "Specific linter to use (e.g., 'ruff', 'eslint')",
101
+ },
102
+ },
103
+ "required": ["path"],
104
+ }
105
+
106
+ async def execute(self, args: Dict[str, Any], ctx: ToolContext) -> ToolResult:
107
+ path = args.get("path", ".")
108
+ severity = args.get("severity", "error")
109
+ specific_linter = args.get("linter")
110
+
111
+ target_path = Path(path)
112
+ if not target_path.is_absolute():
113
+ target_path = ctx.working_directory / target_path
114
+
115
+ if not target_path.exists():
116
+ return ToolResult(success=False, output="", error=f"Path not found: {path}")
117
+
118
+ try:
119
+ # Try LSP first
120
+ lsp_result = await self._try_lsp_diagnostics(target_path, ctx)
121
+ if lsp_result:
122
+ return self._format_lsp_result(lsp_result, severity)
123
+
124
+ # Fall back to running linters directly
125
+ if target_path.is_file():
126
+ language = self._get_language(target_path)
127
+ if not language:
128
+ return ToolResult(
129
+ success=True,
130
+ output="No diagnostics available for this file type",
131
+ metadata={"language": None},
132
+ )
133
+
134
+ return await self._run_linter(target_path, language, ctx, specific_linter)
135
+ else:
136
+ # Directory - find files and check them
137
+ return await self._check_directory(target_path, ctx, severity, specific_linter)
138
+
139
+ except Exception as e:
140
+ return ToolResult(success=False, output="", error=f"Diagnostics error: {str(e)}")
141
+
142
+ def _get_language(self, path: Path) -> Optional[str]:
143
+ """Get language from file extension."""
144
+ return self.EXTENSIONS.get(path.suffix.lower())
145
+
146
+ async def _try_lsp_diagnostics(self, path: Path, ctx: ToolContext) -> Optional[List[Dict]]:
147
+ """Try to get diagnostics from LSP client."""
148
+ try:
149
+ from superqode.lsp.client import LSPClient, LSPConfig
150
+
151
+ # Check if LSP client is available
152
+ client = LSPClient(ctx.working_directory, LSPConfig())
153
+
154
+ if path.is_file():
155
+ language = self._get_language(path)
156
+ if language:
157
+ await client.start_server(language)
158
+ await client.open_file(str(path.relative_to(ctx.working_directory)))
159
+ # Wait for diagnostics
160
+ await asyncio.sleep(1.0)
161
+ diagnostics = await client.get_diagnostics(str(path))
162
+ await client.shutdown()
163
+
164
+ if diagnostics:
165
+ return [
166
+ {
167
+ "file": str(path),
168
+ "line": d.range.start.line + 1,
169
+ "column": d.range.start.character + 1,
170
+ "severity": d.severity_name,
171
+ "message": d.message,
172
+ "source": d.source or "lsp",
173
+ }
174
+ for d in diagnostics
175
+ ]
176
+
177
+ await client.shutdown()
178
+ return None
179
+
180
+ except ImportError:
181
+ return None
182
+ except Exception:
183
+ return None
184
+
185
+ def _format_lsp_result(self, diagnostics: List[Dict], severity_filter: str) -> ToolResult:
186
+ """Format LSP diagnostics as result."""
187
+ # Filter by severity
188
+ severity_order = ["error", "warning", "info", "hint"]
189
+ if severity_filter != "all":
190
+ min_idx = (
191
+ severity_order.index(severity_filter) if severity_filter in severity_order else 0
192
+ )
193
+ diagnostics = [
194
+ d
195
+ for d in diagnostics
196
+ if severity_order.index(d.get("severity", "error")) <= min_idx
197
+ ]
198
+
199
+ if not diagnostics:
200
+ return ToolResult(success=True, output="No diagnostics found", metadata={"count": 0})
201
+
202
+ # Format output
203
+ output_lines = []
204
+ for d in diagnostics:
205
+ output_lines.append(
206
+ f"{d['file']}:{d['line']}:{d['column']}: {d['severity']}: {d['message']}"
207
+ )
208
+
209
+ return ToolResult(
210
+ success=True, output="\n".join(output_lines), metadata={"count": len(diagnostics)}
211
+ )
212
+
213
+ async def _run_linter(
214
+ self, path: Path, language: str, ctx: ToolContext, specific_linter: Optional[str] = None
215
+ ) -> ToolResult:
216
+ """Run a linter directly."""
217
+ linters = self.LINTERS.get(language, [])
218
+
219
+ if specific_linter:
220
+ linters = [(name, cmd) for name, cmd in linters if name == specific_linter]
221
+ if not linters:
222
+ return ToolResult(
223
+ success=False,
224
+ output="",
225
+ error=f"Linter '{specific_linter}' not available for {language}",
226
+ )
227
+
228
+ for linter_name, cmd_template in linters:
229
+ # Check if linter is available
230
+ if not shutil.which(cmd_template[0]):
231
+ continue
232
+
233
+ # Build command
234
+ cmd = cmd_template + [str(path)]
235
+
236
+ try:
237
+ process = await asyncio.create_subprocess_exec(
238
+ *cmd,
239
+ stdout=asyncio.subprocess.PIPE,
240
+ stderr=asyncio.subprocess.PIPE,
241
+ cwd=str(ctx.working_directory),
242
+ )
243
+
244
+ stdout, stderr = await asyncio.wait_for(process.communicate(), timeout=30)
245
+
246
+ output = stdout.decode("utf-8", errors="replace")
247
+ if stderr:
248
+ output += stderr.decode("utf-8", errors="replace")
249
+
250
+ # Parse output
251
+ output = output.strip()
252
+
253
+ if not output:
254
+ return ToolResult(
255
+ success=True,
256
+ output="No diagnostics found",
257
+ metadata={"linter": linter_name, "count": 0},
258
+ )
259
+
260
+ # Count issues (rough heuristic)
261
+ issue_count = len([line for line in output.split("\n") if line.strip()])
262
+
263
+ return ToolResult(
264
+ success=True,
265
+ output=output,
266
+ metadata={"linter": linter_name, "count": issue_count},
267
+ )
268
+
269
+ except asyncio.TimeoutError:
270
+ continue
271
+ except Exception:
272
+ continue
273
+
274
+ return ToolResult(
275
+ success=True,
276
+ output=f"No linters available for {language}",
277
+ metadata={"language": language},
278
+ )
279
+
280
+ async def _check_directory(
281
+ self, path: Path, ctx: ToolContext, severity: str, specific_linter: Optional[str]
282
+ ) -> ToolResult:
283
+ """Check all supported files in a directory."""
284
+ results = []
285
+ files_checked = 0
286
+
287
+ # Find files
288
+ for ext, language in self.EXTENSIONS.items():
289
+ for file_path in path.rglob(f"*{ext}"):
290
+ # Skip common ignore patterns
291
+ parts = file_path.parts
292
+ if any(
293
+ p in ["node_modules", "__pycache__", ".git", "venv", ".venv"] for p in parts
294
+ ):
295
+ continue
296
+
297
+ result = await self._run_linter(file_path, language, ctx, specific_linter)
298
+ files_checked += 1
299
+
300
+ if result.output and result.output != "No diagnostics found":
301
+ results.append(result.output)
302
+
303
+ if not results:
304
+ return ToolResult(
305
+ success=True,
306
+ output=f"No diagnostics found ({files_checked} files checked)",
307
+ metadata={"files_checked": files_checked, "count": 0},
308
+ )
309
+
310
+ output = "\n\n".join(results)
311
+ return ToolResult(success=True, output=output, metadata={"files_checked": files_checked})