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.
- superqode/__init__.py +33 -0
- superqode/acp/__init__.py +23 -0
- superqode/acp/client.py +913 -0
- superqode/acp/permission_screen.py +457 -0
- superqode/acp/types.py +480 -0
- superqode/acp_discovery.py +856 -0
- superqode/agent/__init__.py +22 -0
- superqode/agent/edit_strategies.py +334 -0
- superqode/agent/loop.py +892 -0
- superqode/agent/qe_report_templates.py +39 -0
- superqode/agent/system_prompts.py +353 -0
- superqode/agent_output.py +721 -0
- superqode/agent_stream.py +953 -0
- superqode/agents/__init__.py +59 -0
- superqode/agents/acp_registry.py +305 -0
- superqode/agents/client.py +249 -0
- superqode/agents/data/augmentcode.com.toml +51 -0
- superqode/agents/data/cagent.dev.toml +51 -0
- superqode/agents/data/claude.com.toml +60 -0
- superqode/agents/data/codeassistant.dev.toml +51 -0
- superqode/agents/data/codex.openai.com.toml +57 -0
- superqode/agents/data/fastagent.ai.toml +66 -0
- superqode/agents/data/geminicli.com.toml +77 -0
- superqode/agents/data/goose.block.xyz.toml +54 -0
- superqode/agents/data/junie.jetbrains.com.toml +56 -0
- superqode/agents/data/kimi.moonshot.cn.toml +57 -0
- superqode/agents/data/llmlingagent.dev.toml +51 -0
- superqode/agents/data/molt.bot.toml +49 -0
- superqode/agents/data/opencode.ai.toml +60 -0
- superqode/agents/data/stakpak.dev.toml +51 -0
- superqode/agents/data/vtcode.dev.toml +51 -0
- superqode/agents/discovery.py +266 -0
- superqode/agents/messaging.py +160 -0
- superqode/agents/persona.py +166 -0
- superqode/agents/registry.py +421 -0
- superqode/agents/schema.py +72 -0
- superqode/agents/unified.py +367 -0
- superqode/app/__init__.py +111 -0
- superqode/app/constants.py +314 -0
- superqode/app/css.py +366 -0
- superqode/app/models.py +118 -0
- superqode/app/suggester.py +125 -0
- superqode/app/widgets.py +1591 -0
- superqode/app_enhanced.py +399 -0
- superqode/app_main.py +17187 -0
- superqode/approval.py +312 -0
- superqode/atomic.py +296 -0
- superqode/commands/__init__.py +1 -0
- superqode/commands/acp.py +965 -0
- superqode/commands/agents.py +180 -0
- superqode/commands/auth.py +278 -0
- superqode/commands/config.py +374 -0
- superqode/commands/init.py +826 -0
- superqode/commands/providers.py +819 -0
- superqode/commands/qe.py +1145 -0
- superqode/commands/roles.py +380 -0
- superqode/commands/serve.py +172 -0
- superqode/commands/suggestions.py +127 -0
- superqode/commands/superqe.py +460 -0
- superqode/config/__init__.py +51 -0
- superqode/config/loader.py +812 -0
- superqode/config/schema.py +498 -0
- superqode/core/__init__.py +111 -0
- superqode/core/roles.py +281 -0
- superqode/danger.py +386 -0
- superqode/data/superqode-template.yaml +1522 -0
- superqode/design_system.py +1080 -0
- superqode/dialogs/__init__.py +6 -0
- superqode/dialogs/base.py +39 -0
- superqode/dialogs/model.py +130 -0
- superqode/dialogs/provider.py +870 -0
- superqode/diff_view.py +919 -0
- superqode/enterprise.py +21 -0
- superqode/evaluation/__init__.py +25 -0
- superqode/evaluation/adapters.py +93 -0
- superqode/evaluation/behaviors.py +89 -0
- superqode/evaluation/engine.py +209 -0
- superqode/evaluation/scenarios.py +96 -0
- superqode/execution/__init__.py +36 -0
- superqode/execution/linter.py +538 -0
- superqode/execution/modes.py +347 -0
- superqode/execution/resolver.py +283 -0
- superqode/execution/runner.py +642 -0
- superqode/file_explorer.py +811 -0
- superqode/file_viewer.py +471 -0
- superqode/flash.py +183 -0
- superqode/guidance/__init__.py +58 -0
- superqode/guidance/config.py +203 -0
- superqode/guidance/prompts.py +71 -0
- superqode/harness/__init__.py +54 -0
- superqode/harness/accelerator.py +291 -0
- superqode/harness/config.py +319 -0
- superqode/harness/validator.py +147 -0
- superqode/history.py +279 -0
- superqode/integrations/superopt_runner.py +124 -0
- superqode/logging/__init__.py +49 -0
- superqode/logging/adapters.py +219 -0
- superqode/logging/formatter.py +923 -0
- superqode/logging/integration.py +341 -0
- superqode/logging/sinks.py +170 -0
- superqode/logging/unified_log.py +417 -0
- superqode/lsp/__init__.py +26 -0
- superqode/lsp/client.py +544 -0
- superqode/main.py +1069 -0
- superqode/mcp/__init__.py +89 -0
- superqode/mcp/auth_storage.py +380 -0
- superqode/mcp/client.py +1236 -0
- superqode/mcp/config.py +319 -0
- superqode/mcp/integration.py +337 -0
- superqode/mcp/oauth.py +436 -0
- superqode/mcp/oauth_callback.py +385 -0
- superqode/mcp/types.py +290 -0
- superqode/memory/__init__.py +31 -0
- superqode/memory/feedback.py +342 -0
- superqode/memory/store.py +522 -0
- superqode/notifications.py +369 -0
- superqode/optimization/__init__.py +5 -0
- superqode/optimization/config.py +33 -0
- superqode/permissions/__init__.py +25 -0
- superqode/permissions/rules.py +488 -0
- superqode/plan.py +323 -0
- superqode/providers/__init__.py +33 -0
- superqode/providers/gateway/__init__.py +165 -0
- superqode/providers/gateway/base.py +228 -0
- superqode/providers/gateway/litellm_gateway.py +1170 -0
- superqode/providers/gateway/openresponses_gateway.py +436 -0
- superqode/providers/health.py +297 -0
- superqode/providers/huggingface/__init__.py +74 -0
- superqode/providers/huggingface/downloader.py +472 -0
- superqode/providers/huggingface/endpoints.py +442 -0
- superqode/providers/huggingface/hub.py +531 -0
- superqode/providers/huggingface/inference.py +394 -0
- superqode/providers/huggingface/transformers_runner.py +516 -0
- superqode/providers/local/__init__.py +100 -0
- superqode/providers/local/base.py +438 -0
- superqode/providers/local/discovery.py +418 -0
- superqode/providers/local/lmstudio.py +256 -0
- superqode/providers/local/mlx.py +457 -0
- superqode/providers/local/ollama.py +486 -0
- superqode/providers/local/sglang.py +268 -0
- superqode/providers/local/tgi.py +260 -0
- superqode/providers/local/tool_support.py +477 -0
- superqode/providers/local/vllm.py +258 -0
- superqode/providers/manager.py +1338 -0
- superqode/providers/models.py +1016 -0
- superqode/providers/models_dev.py +578 -0
- superqode/providers/openresponses/__init__.py +87 -0
- superqode/providers/openresponses/converters/__init__.py +17 -0
- superqode/providers/openresponses/converters/messages.py +343 -0
- superqode/providers/openresponses/converters/tools.py +268 -0
- superqode/providers/openresponses/schema/__init__.py +56 -0
- superqode/providers/openresponses/schema/models.py +585 -0
- superqode/providers/openresponses/streaming/__init__.py +5 -0
- superqode/providers/openresponses/streaming/parser.py +338 -0
- superqode/providers/openresponses/tools/__init__.py +21 -0
- superqode/providers/openresponses/tools/apply_patch.py +352 -0
- superqode/providers/openresponses/tools/code_interpreter.py +290 -0
- superqode/providers/openresponses/tools/file_search.py +333 -0
- superqode/providers/openresponses/tools/mcp_adapter.py +252 -0
- superqode/providers/registry.py +716 -0
- superqode/providers/usage.py +332 -0
- superqode/pure_mode.py +384 -0
- superqode/qr/__init__.py +23 -0
- superqode/qr/dashboard.py +781 -0
- superqode/qr/generator.py +1018 -0
- superqode/qr/templates.py +135 -0
- superqode/safety/__init__.py +41 -0
- superqode/safety/sandbox.py +413 -0
- superqode/safety/warnings.py +256 -0
- superqode/server/__init__.py +33 -0
- superqode/server/lsp_server.py +775 -0
- superqode/server/web.py +250 -0
- superqode/session/__init__.py +25 -0
- superqode/session/persistence.py +580 -0
- superqode/session/sharing.py +477 -0
- superqode/session.py +475 -0
- superqode/sidebar.py +2991 -0
- superqode/stream_view.py +648 -0
- superqode/styles/__init__.py +3 -0
- superqode/superqe/__init__.py +184 -0
- superqode/superqe/acp_runner.py +1064 -0
- superqode/superqe/constitution/__init__.py +62 -0
- superqode/superqe/constitution/evaluator.py +308 -0
- superqode/superqe/constitution/loader.py +432 -0
- superqode/superqe/constitution/schema.py +250 -0
- superqode/superqe/events.py +591 -0
- superqode/superqe/frameworks/__init__.py +65 -0
- superqode/superqe/frameworks/base.py +234 -0
- superqode/superqe/frameworks/e2e.py +263 -0
- superqode/superqe/frameworks/executor.py +237 -0
- superqode/superqe/frameworks/javascript.py +409 -0
- superqode/superqe/frameworks/python.py +373 -0
- superqode/superqe/frameworks/registry.py +92 -0
- superqode/superqe/mcp_tools/__init__.py +47 -0
- superqode/superqe/mcp_tools/core_tools.py +418 -0
- superqode/superqe/mcp_tools/registry.py +230 -0
- superqode/superqe/mcp_tools/testing_tools.py +167 -0
- superqode/superqe/noise.py +89 -0
- superqode/superqe/orchestrator.py +778 -0
- superqode/superqe/roles.py +609 -0
- superqode/superqe/session.py +713 -0
- superqode/superqe/skills/__init__.py +57 -0
- superqode/superqe/skills/base.py +106 -0
- superqode/superqe/skills/core_skills.py +899 -0
- superqode/superqe/skills/registry.py +90 -0
- superqode/superqe/verifier.py +101 -0
- superqode/superqe_cli.py +76 -0
- superqode/tool_call.py +358 -0
- superqode/tools/__init__.py +93 -0
- superqode/tools/agent_tools.py +496 -0
- superqode/tools/base.py +324 -0
- superqode/tools/batch_tool.py +133 -0
- superqode/tools/diagnostics.py +311 -0
- superqode/tools/edit_tools.py +653 -0
- superqode/tools/enhanced_base.py +515 -0
- superqode/tools/file_tools.py +269 -0
- superqode/tools/file_tracking.py +45 -0
- superqode/tools/lsp_tools.py +610 -0
- superqode/tools/network_tools.py +350 -0
- superqode/tools/permissions.py +400 -0
- superqode/tools/question_tool.py +324 -0
- superqode/tools/search_tools.py +598 -0
- superqode/tools/shell_tools.py +259 -0
- superqode/tools/todo_tools.py +121 -0
- superqode/tools/validation.py +80 -0
- superqode/tools/web_tools.py +639 -0
- superqode/tui.py +1152 -0
- superqode/tui_integration.py +875 -0
- superqode/tui_widgets/__init__.py +27 -0
- superqode/tui_widgets/widgets/__init__.py +18 -0
- superqode/tui_widgets/widgets/progress.py +185 -0
- superqode/tui_widgets/widgets/tool_display.py +188 -0
- superqode/undo_manager.py +574 -0
- superqode/utils/__init__.py +5 -0
- superqode/utils/error_handling.py +323 -0
- superqode/utils/fuzzy.py +257 -0
- superqode/widgets/__init__.py +477 -0
- superqode/widgets/agent_collab.py +390 -0
- superqode/widgets/agent_store.py +936 -0
- superqode/widgets/agent_switcher.py +395 -0
- superqode/widgets/animation_manager.py +284 -0
- superqode/widgets/code_context.py +356 -0
- superqode/widgets/command_palette.py +412 -0
- superqode/widgets/connection_status.py +537 -0
- superqode/widgets/conversation_history.py +470 -0
- superqode/widgets/diff_indicator.py +155 -0
- superqode/widgets/enhanced_status_bar.py +385 -0
- superqode/widgets/enhanced_toast.py +476 -0
- superqode/widgets/file_browser.py +809 -0
- superqode/widgets/file_reference.py +585 -0
- superqode/widgets/issue_timeline.py +340 -0
- superqode/widgets/leader_key.py +264 -0
- superqode/widgets/mode_switcher.py +445 -0
- superqode/widgets/model_picker.py +234 -0
- superqode/widgets/permission_preview.py +1205 -0
- superqode/widgets/prompt.py +358 -0
- superqode/widgets/provider_connect.py +725 -0
- superqode/widgets/pty_shell.py +587 -0
- superqode/widgets/qe_dashboard.py +321 -0
- superqode/widgets/resizable_sidebar.py +377 -0
- superqode/widgets/response_changes.py +218 -0
- superqode/widgets/response_display.py +528 -0
- superqode/widgets/rich_tool_display.py +613 -0
- superqode/widgets/sidebar_panels.py +1180 -0
- superqode/widgets/slash_complete.py +356 -0
- superqode/widgets/split_view.py +612 -0
- superqode/widgets/status_bar.py +273 -0
- superqode/widgets/superqode_display.py +786 -0
- superqode/widgets/thinking_display.py +815 -0
- superqode/widgets/throbber.py +87 -0
- superqode/widgets/toast.py +206 -0
- superqode/widgets/unified_output.py +1073 -0
- superqode/workspace/__init__.py +75 -0
- superqode/workspace/artifacts.py +472 -0
- superqode/workspace/coordinator.py +353 -0
- superqode/workspace/diff_tracker.py +429 -0
- superqode/workspace/git_guard.py +373 -0
- superqode/workspace/git_snapshot.py +526 -0
- superqode/workspace/manager.py +750 -0
- superqode/workspace/snapshot.py +357 -0
- superqode/workspace/watcher.py +535 -0
- superqode/workspace/worktree.py +440 -0
- superqode-0.1.5.dist-info/METADATA +204 -0
- superqode-0.1.5.dist-info/RECORD +288 -0
- superqode-0.1.5.dist-info/WHEEL +5 -0
- superqode-0.1.5.dist-info/entry_points.txt +3 -0
- superqode-0.1.5.dist-info/licenses/LICENSE +648 -0
- 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})
|