skydeckai-code 0.1.29__py3-none-any.whl → 0.1.31__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.
aidd/tools/__init__.py CHANGED
@@ -65,6 +65,7 @@ from .git_tools import (
65
65
  handle_git_status,
66
66
  )
67
67
  from .image_tools import read_image_file_tool, handle_read_image_file
68
+ from .lint_tools import check_lint_tool, handle_check_lint
68
69
  from .other_tools import batch_tools_tool, handle_batch_tools, think_tool, handle_think
69
70
  from .path_tools import (
70
71
  get_allowed_directory_tool,
@@ -99,6 +100,7 @@ TOOL_DEFINITIONS = [
99
100
  execute_shell_script_tool(),
100
101
  codebase_mapper_tool(),
101
102
  search_code_tool(),
103
+ check_lint_tool(),
102
104
  batch_tools_tool(),
103
105
  think_tool(),
104
106
  # Git tools
@@ -142,6 +144,7 @@ TOOL_HANDLERS = {
142
144
  "copy_file": handle_copy_file,
143
145
  "search_files": handle_search_files,
144
146
  "search_code": handle_search_code,
147
+ "check_lint": handle_check_lint,
145
148
  "delete_file": handle_delete_file,
146
149
  "get_file_info": handle_get_file_info,
147
150
  "directory_tree": handle_directory_tree,
aidd/tools/code_tools.py CHANGED
@@ -2,6 +2,7 @@ import os
2
2
  import re
3
3
  import fnmatch
4
4
  import subprocess
5
+ import json
5
6
  from datetime import datetime
6
7
  from typing import List, Dict, Any, Optional, Union, Tuple
7
8
 
aidd/tools/file_tools.py CHANGED
@@ -283,16 +283,6 @@ def edit_file_tool():
283
283
  "options": {
284
284
  "type": "object",
285
285
  "properties": {
286
- "preserveIndentation": {
287
- "type": "boolean",
288
- "description": "Keep existing indentation when replacing text. When true, the indentation of the first line of oldText is preserved in newText.",
289
- "default": True
290
- },
291
- "normalizeWhitespace": {
292
- "type": "boolean",
293
- "description": "Normalize spaces while preserving structure. When true, consecutive spaces are treated as a single space during matching, making the search more forgiving of whitespace differences.",
294
- "default": True
295
- },
296
286
  "partialMatch": {
297
287
  "type": "boolean",
298
288
  "description": "Enable fuzzy matching for finding text. When true, the tool will try to find the best match even if it's not an exact match, using a confidence threshold of 80%.",
@@ -730,21 +720,17 @@ async def handle_delete_file(arguments: dict):
730
720
  except Exception as e:
731
721
  raise ValueError(f"Error deleting {path}: {str(e)}")
732
722
 
733
- def normalize_whitespace(text: str, preserve_indentation: bool = True) -> str:
734
- """Normalize whitespace while optionally preserving indentation."""
723
+ def normalize_whitespace(text: str) -> str:
724
+ """Normalize whitespace while preserving indentation."""
735
725
  lines = text.splitlines()
736
726
  normalized_lines = []
737
727
 
738
728
  for line in lines:
739
- if preserve_indentation:
740
- # Preserve leading whitespace
741
- indent = re.match(r'^\s*', line).group(0)
742
- # Normalize other whitespace
743
- content = re.sub(r'\s+', ' ', line.lstrip())
744
- normalized_lines.append(f"{indent}{content}")
745
- else:
746
- # Normalize all whitespace
747
- normalized_lines.append(re.sub(r'\s+', ' ', line.strip()))
729
+ # Preserve leading whitespace
730
+ indent = re.match(r'^\s*', line).group(0)
731
+ # Normalize other whitespace
732
+ content = re.sub(r'\s+', ' ', line.lstrip())
733
+ normalized_lines.append(f"{indent}{content}")
748
734
 
749
735
  return '\n'.join(normalized_lines)
750
736
 
@@ -813,8 +799,6 @@ async def apply_file_edits(file_path: str, edits: List[dict], dry_run: bool = Fa
813
799
  """Apply edits to a file with optional formatting and return diff."""
814
800
  # Set default options
815
801
  options = options or {}
816
- preserve_indentation = options.get('preserveIndentation', True)
817
- normalize_ws = options.get('normalizeWhitespace', True)
818
802
  partial_match = options.get('partialMatch', True)
819
803
 
820
804
  # Read file content
@@ -830,20 +814,16 @@ async def apply_file_edits(file_path: str, edits: List[dict], dry_run: bool = Fa
830
814
  old_text = edit['oldText']
831
815
  new_text = edit['newText']
832
816
 
833
- # Normalize texts if requested
834
- if normalize_ws:
835
- search_text = normalize_whitespace(old_text, preserve_indentation)
836
- working_content = normalize_whitespace(modified_content, preserve_indentation)
837
- else:
838
- search_text = old_text
839
- working_content = modified_content
817
+ # Use original text for matching
818
+ search_text = old_text
819
+ working_content = modified_content
840
820
 
841
821
  # Find best match
842
822
  start, end, confidence = find_best_match(working_content, search_text, partial_match)
843
823
 
844
824
  if confidence >= 0.8:
845
- # Preserve indentation of first line if requested
846
- if preserve_indentation and start >= 0:
825
+ # Always preserve indentation of first line
826
+ if start >= 0:
847
827
  indent = re.match(r'^\s*', modified_content[start:].splitlines()[0]).group(0)
848
828
  replacement = '\n'.join(indent + line.lstrip()
849
829
  for line in new_text.splitlines())
@@ -0,0 +1,590 @@
1
+ import os
2
+ import re
3
+ import json
4
+ import subprocess
5
+ from typing import List, Dict, Any, Optional
6
+
7
+ from mcp.types import TextContent
8
+ from .state import state
9
+
10
+
11
+ def check_lint_tool():
12
+ return {
13
+ "name": "check_lint",
14
+ "description": "Check the codebase for linting issues using native linting tools. "
15
+ "WHEN TO USE: When you need to identify coding style issues, potential bugs, or code quality problems. "
16
+ "Similar to how VSCode reports lint issues in the Problems panel. "
17
+ "WHEN NOT TO USE: When you need to fix formatting (use appropriate formatters instead), "
18
+ "when you need detailed code analysis with custom rules, or for compiled languages where linting may not apply. "
19
+ "RETURNS: A detailed report of linting issues found in the codebase, including file paths, line numbers, "
20
+ "issue descriptions, and severity levels. Issues are grouped by file and sorted by severity. "
21
+ "Note: Respects config files like .pylintrc, .flake8, .eslintrc, and analysis_options.yaml if present.\n\n"
22
+ "EXAMPLES:\n"
23
+ "- Basic usage: {\"path\": \"src\"}\n"
24
+ "- Python with custom line length: {\"path\": \"src\", \"linters\": {\"flake8\": \"--max-line-length=120 --ignore=E501,E302\"}}\n"
25
+ "- Disable specific pylint checks: {\"linters\": {\"pylint\": \"--disable=missing-docstring,invalid-name\"}}\n"
26
+ "- TypeScript only: {\"path\": \"src\", \"languages\": [\"typescript\"], \"linters\": {\"eslint\": \"--no-eslintrc --config .eslintrc.custom.js\"}}\n"
27
+ "- Dart only: {\"path\": \"lib\", \"languages\": [\"dart\"], \"linters\": {\"dart_analyze\": \"--fatal-infos\"}}\n"
28
+ "- Disable a linter: {\"linters\": {\"flake8\": false}, \"max_issues\": 50}\n"
29
+ "- Single file check: {\"path\": \"src/main.py\"}",
30
+ "inputSchema": {
31
+ "type": "object",
32
+ "properties": {
33
+ "path": {
34
+ "type": "string",
35
+ "description": "Directory or file to lint. Can be a specific file or directory to recursively check. "
36
+ "Examples: '.' for entire codebase, 'src' for just the src directory, 'src/main.py' for a specific file.",
37
+ "default": "."
38
+ },
39
+ "languages": {
40
+ "type": "array",
41
+ "items": {
42
+ "type": "string"
43
+ },
44
+ "description": "List of languages to lint. If empty, will auto-detect based on file extensions. "
45
+ "Supported languages include: 'python', 'javascript', 'typescript', 'dart'.",
46
+ "default": []
47
+ },
48
+ "linters": {
49
+ "type": "object",
50
+ "description": "Configuration for specific linters. Each key is a linter name ('pylint', 'flake8', 'eslint', 'dart_analyze') "
51
+ "and the value is either a boolean to enable/disable or a string with CLI arguments.",
52
+ "properties": {
53
+ "pylint": {
54
+ "type": ["boolean", "string"],
55
+ "description": "Whether to use pylint or custom pylint arguments."
56
+ },
57
+ "flake8": {
58
+ "type": ["boolean", "string"],
59
+ "description": "Whether to use flake8 or custom flake8 arguments."
60
+ },
61
+ "eslint": {
62
+ "type": ["boolean", "string"],
63
+ "description": "Whether to use eslint or custom eslint arguments."
64
+ },
65
+ "dart_analyze": {
66
+ "type": ["boolean", "string"],
67
+ "description": "Whether to use 'dart analyze' or custom dart analyze arguments."
68
+ }
69
+ },
70
+ "default": {}
71
+ },
72
+ "max_issues": {
73
+ "type": "integer",
74
+ "description": "Maximum number of issues to return. Set to 0 for unlimited.",
75
+ "default": 100
76
+ }
77
+ },
78
+ "required": []
79
+ }
80
+ }
81
+
82
+ async def handle_check_lint(arguments: dict) -> List[TextContent]:
83
+ """Handle linting the codebase and reporting issues using native linting tools."""
84
+ path = arguments.get("path", ".")
85
+ languages = arguments.get("languages", [])
86
+ linters_config = arguments.get("linters", {})
87
+ max_issues = arguments.get("max_issues", 100)
88
+
89
+ # Determine full path
90
+ if os.path.isabs(path):
91
+ full_path = os.path.abspath(path)
92
+ else:
93
+ full_path = os.path.abspath(os.path.join(state.allowed_directory, path))
94
+
95
+ # Security check
96
+ if not full_path.startswith(state.allowed_directory):
97
+ raise ValueError(f"Access denied: Path ({full_path}) must be within allowed directory")
98
+
99
+ if not os.path.exists(full_path):
100
+ raise ValueError(f"Path does not exist: {path}")
101
+
102
+ # Skip the entire operation if the path looks like a virtual environment or system directory
103
+ if _is_excluded_system_directory(full_path):
104
+ return [TextContent(
105
+ type="text",
106
+ text="The specified path appears to be a virtual environment, package directory, or system path.\n"
107
+ "To prevent excessive noise, linting has been skipped for this path.\n"
108
+ "Please specify a project source directory to lint instead."
109
+ )]
110
+
111
+ # Auto-detect languages if not specified
112
+ if not languages:
113
+ if os.path.isfile(full_path):
114
+ language = _detect_language_from_file(full_path)
115
+ if language:
116
+ languages = [language]
117
+ else:
118
+ # If we can't detect a supported language for the file
119
+ _, ext = os.path.splitext(full_path)
120
+ return [TextContent(
121
+ type="text",
122
+ text=f"Unsupported file type: {ext}\nThe check_lint tool only supports: .py, .js, .jsx, .ts, .tsx, .dart files.\nSupported languages are: python, javascript, typescript, dart."
123
+ )]
124
+ else:
125
+ languages = ["python", "javascript", "typescript", "dart"] # Default to common languages
126
+
127
+ # Prepare linter configurations with defaults
128
+ linter_defaults = {
129
+ "python": {
130
+ "pylint": True,
131
+ "flake8": True
132
+ },
133
+ "javascript": {
134
+ "eslint": True
135
+ },
136
+ "typescript": {
137
+ "eslint": True
138
+ },
139
+ "dart": {
140
+ "dart_analyze": True
141
+ }
142
+ }
143
+
144
+ # Validate languages if explicitly specified
145
+ if languages and os.path.isfile(full_path):
146
+ _, ext = os.path.splitext(full_path)
147
+ ext_language_map = {
148
+ '.py': 'python',
149
+ '.js': 'javascript',
150
+ '.jsx': 'javascript',
151
+ '.ts': 'typescript',
152
+ '.tsx': 'typescript',
153
+ '.dart': 'dart',
154
+ '.flutter': 'dart'
155
+ }
156
+ detected_language = ext_language_map.get(ext.lower())
157
+
158
+ # If we have a mismatch between specified language and file extension
159
+ if detected_language and not any(lang == detected_language for lang in languages):
160
+ return [TextContent(
161
+ type="text",
162
+ text=f"Language mismatch: File has {ext} extension but you specified {languages}.\n"
163
+ f"For this file extension, please use: {detected_language}"
164
+ )]
165
+
166
+ # If file type is not supported at all
167
+ if not detected_language:
168
+ return [TextContent(
169
+ type="text",
170
+ text=f"Unsupported file type: {ext}\nThe check_lint tool only supports: .py, .js, .jsx, .ts, .tsx, .dart files.\nSupported languages are: python, javascript, typescript, dart."
171
+ )]
172
+
173
+ # Process each language
174
+ all_issues = []
175
+
176
+ for language in languages:
177
+ if language in linter_defaults:
178
+ # Get default linters for this language
179
+ default_linters = linter_defaults[language]
180
+
181
+ # Override with user configuration
182
+ for linter_name, default_value in default_linters.items():
183
+ # Check if the linter is explicitly configured
184
+ if linter_name in linters_config:
185
+ linter_setting = linters_config[linter_name]
186
+
187
+ # Skip if explicitly disabled
188
+ if linter_setting is False:
189
+ continue
190
+
191
+ # Run the linter with custom args or defaults
192
+ issues = await _run_linter(
193
+ linter_name,
194
+ full_path,
195
+ custom_args=linter_setting if isinstance(linter_setting, str) else None
196
+ )
197
+ all_issues.extend(issues)
198
+ elif default_value:
199
+ # Use default if not explicitly configured
200
+ issues = await _run_linter(linter_name, full_path)
201
+ all_issues.extend(issues)
202
+
203
+ # Limit total issues if needed
204
+ if max_issues > 0 and len(all_issues) >= max_issues:
205
+ all_issues = all_issues[:max_issues]
206
+ break
207
+
208
+ # Format and return results
209
+ if not all_issues:
210
+ return [TextContent(
211
+ type="text",
212
+ text="No linting issues found."
213
+ )]
214
+
215
+ return [TextContent(
216
+ type="text",
217
+ text=_format_lint_results(all_issues)
218
+ )]
219
+
220
+ def _is_excluded_system_directory(path: str) -> bool:
221
+ """Check if the path is a system directory or virtual environment that should be excluded from linting."""
222
+ # Common virtual environment directories
223
+ venv_indicators = [
224
+ 'venv', '.venv', 'env', '.env', 'virtualenv',
225
+ 'site-packages', 'dist-packages',
226
+ # Python version-specific directories often in venvs
227
+ 'python3', 'python2', 'python3.', 'lib/python'
228
+ ]
229
+
230
+ # System and dependency directories to exclude
231
+ system_dirs = [
232
+ 'node_modules', 'bower_components',
233
+ '.git', '.svn',
234
+ '__pycache__', '.mypy_cache', '.pytest_cache', '.ruff_cache',
235
+ '.idea', '.vscode'
236
+ ]
237
+
238
+ # Check if the path contains any of these indicators
239
+ path_parts = path.lower().split(os.sep)
240
+
241
+ # Check for virtual environment indicators
242
+ for indicator in venv_indicators:
243
+ if indicator in path_parts:
244
+ return True
245
+
246
+ # Check for system directories
247
+ for sys_dir in system_dirs:
248
+ if sys_dir in path_parts:
249
+ return True
250
+
251
+ return False
252
+
253
+ async def _run_linter(linter_name: str, path: str, custom_args: str = None) -> List[Dict[str, Any]]:
254
+ """Run a specific linter as a command-line process and parse the results."""
255
+ issues = []
256
+
257
+ # Skip if the path is a system directory
258
+ if _is_excluded_system_directory(path):
259
+ return []
260
+
261
+ # Handle different linters
262
+ if linter_name == "pylint":
263
+ try:
264
+ # Build the command
265
+ cmd = ["pylint", "--output-format=json"]
266
+
267
+ if custom_args:
268
+ # Split and add custom arguments
269
+ cmd.extend(custom_args.split())
270
+
271
+ # Add the target path
272
+ if os.path.isdir(path):
273
+ # For directories, scan recursively but exclude common directories
274
+ cmd.extend([
275
+ "--recursive=y",
276
+ "--ignore=venv,.venv,env,.env,node_modules,__pycache__,.git,.svn,dist,build,target,.idea,.vscode",
277
+ ])
278
+
279
+ cmd.append(path)
280
+
281
+ # Run pylint
282
+ result = subprocess.run(cmd, capture_output=True, text=True)
283
+
284
+ if result.stdout.strip():
285
+ # Parse pylint JSON output
286
+ try:
287
+ pylint_issues = json.loads(result.stdout)
288
+ for issue in pylint_issues:
289
+ # Extract the file path and ensure it's within the allowed directory
290
+ file_path = issue.get("path", "")
291
+ if not os.path.isabs(file_path):
292
+ file_path = os.path.abspath(os.path.join(os.path.dirname(path), file_path))
293
+
294
+ # Security check for file path and exclude system directories
295
+ if not file_path.startswith(state.allowed_directory) or _is_excluded_system_directory(file_path):
296
+ continue
297
+
298
+ issues.append({
299
+ "file": os.path.relpath(file_path, state.allowed_directory),
300
+ "line": issue.get("line", 0),
301
+ "column": issue.get("column", 0),
302
+ "message": issue.get("message", ""),
303
+ "severity": _map_pylint_severity(issue.get("type", "")),
304
+ "source": "pylint",
305
+ "code": issue.get("symbol", "")
306
+ })
307
+ except (json.JSONDecodeError, ValueError):
308
+ # If JSON parsing fails, it might be an error message
309
+ if result.stderr:
310
+ issues.append({
311
+ "file": path if os.path.isfile(path) else "",
312
+ "line": 1,
313
+ "column": 1,
314
+ "message": f"Error running pylint: {result.stderr}",
315
+ "severity": "error",
316
+ "source": "pylint",
317
+ "code": "tool-error"
318
+ })
319
+ except (subprocess.SubprocessError, FileNotFoundError):
320
+ # Silently fail if pylint is not installed
321
+ pass
322
+
323
+ elif linter_name == "flake8":
324
+ try:
325
+ # Build the command
326
+ cmd = ["flake8", "--format=default"]
327
+
328
+ if custom_args:
329
+ cmd.extend(custom_args.split())
330
+ else:
331
+ # Add default exclusions if no custom args provided
332
+ cmd.extend(["--exclude=.venv,venv,env,.env,node_modules,__pycache__,.git,.svn,dist,build,target,.idea,.vscode"])
333
+
334
+ # Add target path
335
+ cmd.append(path)
336
+
337
+ # Run flake8
338
+ result = subprocess.run(cmd, capture_output=True, text=True)
339
+
340
+ if result.stdout.strip():
341
+ # Parse flake8 output (file:line:col: code message)
342
+ flake8_pattern = r"(.+):(\d+):(\d+): ([A-Z]\d+) (.+)"
343
+ for line in result.stdout.splitlines():
344
+ match = re.match(flake8_pattern, line)
345
+ if match:
346
+ filepath, line_num, col, code, message = match.groups()
347
+
348
+ # Ensure path is absolute for security check
349
+ if not os.path.isabs(filepath):
350
+ filepath = os.path.abspath(os.path.join(os.path.dirname(path), filepath))
351
+
352
+ # Security check for file path and exclude system directories
353
+ if not filepath.startswith(state.allowed_directory) or _is_excluded_system_directory(filepath):
354
+ continue
355
+
356
+ issues.append({
357
+ "file": os.path.relpath(filepath, state.allowed_directory),
358
+ "line": int(line_num),
359
+ "column": int(col),
360
+ "message": message,
361
+ "severity": "warning", # flake8 doesn't provide severity
362
+ "source": "flake8",
363
+ "code": code
364
+ })
365
+ except (subprocess.SubprocessError, FileNotFoundError):
366
+ # Silently fail if flake8 is not installed
367
+ pass
368
+
369
+ elif linter_name == "eslint":
370
+ try:
371
+ # Build the command
372
+ cmd = ["npx", "eslint", "--format=json"]
373
+
374
+ if custom_args:
375
+ cmd.extend(custom_args.split())
376
+ else:
377
+ # Add default exclusions if no custom args provided
378
+ cmd.extend(["--ignore-pattern", "**/node_modules/**", "--ignore-pattern", "**/.git/**"])
379
+
380
+ # Add target path
381
+ cmd.append(path)
382
+
383
+ # Run ESLint
384
+ result = subprocess.run(cmd, capture_output=True, text=True)
385
+
386
+ if result.stdout.strip():
387
+ # Parse ESLint JSON output
388
+ try:
389
+ eslint_results = json.loads(result.stdout)
390
+ for file_result in eslint_results:
391
+ # Get file path and ensure it's absolute
392
+ file_path = file_result.get("filePath", "")
393
+ if not os.path.isabs(file_path):
394
+ file_path = os.path.abspath(os.path.join(os.path.dirname(path), file_path))
395
+
396
+ # Security check for file path and exclude system directories
397
+ if not file_path.startswith(state.allowed_directory) or _is_excluded_system_directory(file_path):
398
+ continue
399
+
400
+ for message in file_result.get("messages", []):
401
+ issues.append({
402
+ "file": os.path.relpath(file_path, state.allowed_directory),
403
+ "line": message.get("line", 0),
404
+ "column": message.get("column", 0),
405
+ "message": message.get("message", ""),
406
+ "severity": _map_eslint_severity(message.get("severity", 1)),
407
+ "source": "eslint",
408
+ "code": message.get("ruleId", "")
409
+ })
410
+ except json.JSONDecodeError:
411
+ # Handle parsing errors or ESLint errors
412
+ if result.stderr:
413
+ issues.append({
414
+ "file": path if os.path.isfile(path) else "",
415
+ "line": 1,
416
+ "column": 1,
417
+ "message": f"Error running eslint: {result.stderr}",
418
+ "severity": "error",
419
+ "source": "eslint",
420
+ "code": "tool-error"
421
+ })
422
+ except (subprocess.SubprocessError, FileNotFoundError):
423
+ # Silently fail if eslint is not installed
424
+ pass
425
+
426
+ elif linter_name == "dart_analyze":
427
+ try:
428
+ # Build the command
429
+ cmd = ["dart", "analyze", "--format=json"]
430
+
431
+ if custom_args:
432
+ cmd.extend(custom_args.split())
433
+
434
+ # Add target path
435
+ cmd.append(path)
436
+
437
+ # Run dart analyze
438
+ result = subprocess.run(cmd, capture_output=True, text=True)
439
+
440
+ if result.stdout.strip():
441
+ # Parse Dart Analyze JSON output
442
+ try:
443
+ dart_results = json.loads(result.stdout)
444
+ for file_result in dart_results.get("issues", []):
445
+ # Get file path and ensure it's absolute
446
+ file_path = file_result.get("path", "")
447
+ if not os.path.isabs(file_path):
448
+ file_path = os.path.abspath(os.path.join(os.path.dirname(path), file_path))
449
+
450
+ # Security check for file path and exclude system directories
451
+ if not file_path.startswith(state.allowed_directory) or _is_excluded_system_directory(file_path):
452
+ continue
453
+
454
+ issues.append({
455
+ "file": os.path.relpath(file_path, state.allowed_directory),
456
+ "line": file_result.get("location", {}).get("startLine", 0),
457
+ "column": file_result.get("location", {}).get("startColumn", 0),
458
+ "message": file_result.get("message", ""),
459
+ "severity": _map_dart_severity(file_result.get("severity", "")),
460
+ "source": "dart",
461
+ "code": file_result.get("code", "")
462
+ })
463
+ except json.JSONDecodeError:
464
+ # Try parsing Flutter-specific error format (from compilation errors)
465
+ try:
466
+ # For flutter/dart compilation errors which might not be in JSON format
467
+ dart_issues = []
468
+ for line in result.stdout.splitlines() + result.stderr.splitlines():
469
+ # Check if line contains a compilation error pattern
470
+ error_match = re.search(r'(.*?):(\d+):(\d+):\s+(error|warning|info):\s+(.*)', line)
471
+ if error_match:
472
+ file_path, line_num, col, severity, message = error_match.groups()
473
+
474
+ # Ensure path is absolute for security check
475
+ if not os.path.isabs(file_path):
476
+ file_path = os.path.abspath(os.path.join(os.path.dirname(path), file_path))
477
+
478
+ # Security check for file path
479
+ if not file_path.startswith(state.allowed_directory) or _is_excluded_system_directory(file_path):
480
+ continue
481
+
482
+ dart_issues.append({
483
+ "file": os.path.relpath(file_path, state.allowed_directory),
484
+ "line": int(line_num),
485
+ "column": int(col),
486
+ "message": message,
487
+ "severity": severity,
488
+ "source": "dart",
489
+ "code": "compilation-error"
490
+ })
491
+
492
+ # If we found compilation errors, add them
493
+ issues.extend(dart_issues)
494
+ except Exception:
495
+ # Handle any parsing errors
496
+ if result.stderr:
497
+ issues.append({
498
+ "file": path if os.path.isfile(path) else "",
499
+ "line": 1,
500
+ "column": 1,
501
+ "message": f"Error running dart analyze: {result.stderr}",
502
+ "severity": "error",
503
+ "source": "dart",
504
+ "code": "tool-error"
505
+ })
506
+ except (subprocess.SubprocessError, FileNotFoundError):
507
+ # Silently fail if dart is not installed
508
+ pass
509
+
510
+ return issues
511
+
512
+ def _detect_language_from_file(file_path: str) -> Optional[str]:
513
+ """Detect the programming language based on file extension."""
514
+ _, ext = os.path.splitext(file_path)
515
+ ext = ext.lower()
516
+
517
+ language_map = {
518
+ '.py': 'python',
519
+ '.js': 'javascript',
520
+ '.jsx': 'javascript',
521
+ '.ts': 'typescript',
522
+ '.tsx': 'typescript',
523
+ '.dart': 'dart',
524
+ '.flutter': 'dart'
525
+ }
526
+
527
+ return language_map.get(ext)
528
+
529
+ def _map_pylint_severity(severity_type: str) -> str:
530
+ """Map pylint message types to standard severity levels."""
531
+ severity_map = {
532
+ "convention": "hint",
533
+ "refactor": "info",
534
+ "warning": "warning",
535
+ "error": "error",
536
+ "fatal": "error"
537
+ }
538
+ return severity_map.get(severity_type.lower(), "info")
539
+
540
+ def _map_eslint_severity(severity: int) -> str:
541
+ """Map ESLint severity levels to standard severity levels."""
542
+ if severity == 2:
543
+ return "error"
544
+ elif severity == 1:
545
+ return "warning"
546
+ else:
547
+ return "info"
548
+
549
+ def _map_dart_severity(severity_type: str) -> str:
550
+ """Map dart severity levels to standard severity levels."""
551
+ severity_map = {
552
+ "info": "info",
553
+ "warning": "warning",
554
+ "error": "error",
555
+ }
556
+ return severity_map.get(severity_type.lower(), "info")
557
+
558
+ def _format_lint_results(issues: List[Dict[str, Any]]) -> str:
559
+ """Format linting issues into a readable text output."""
560
+ if not issues:
561
+ return "No linting issues found."
562
+
563
+ # Group issues by file
564
+ issues_by_file = {}
565
+ for issue in issues:
566
+ file_path = issue["file"]
567
+ if file_path not in issues_by_file:
568
+ issues_by_file[file_path] = []
569
+ issues_by_file[file_path].append(issue)
570
+
571
+ # Format the output
572
+ output_lines = ["Linting issues found:"]
573
+
574
+ for file_path, file_issues in issues_by_file.items():
575
+ # Sort issues by severity (error -> warning -> info -> hint)
576
+ severity_order = {"error": 0, "warning": 1, "info": 2, "hint": 3}
577
+ sorted_issues = sorted(file_issues, key=lambda x: (severity_order.get(x["severity"], 4), x["line"]))
578
+
579
+ output_lines.append(f"\n{file_path}:")
580
+
581
+ for issue in sorted_issues:
582
+ severity = issue["severity"].upper()
583
+ line = issue["line"]
584
+ column = issue["column"]
585
+ message = issue["message"]
586
+ code = f"[{issue['code']}]" if issue["code"] else ""
587
+
588
+ output_lines.append(f" {severity} Line {line}:{column} {message} {code}")
589
+
590
+ return "\n".join(output_lines)
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: skydeckai-code
3
- Version: 0.1.29
4
- Summary: This MCP server provides a comprehensive set of tools for AI-driven Development workflows including file operations, code analysis, multi-language execution, Git operations, web content fetching with HTML-to-markdown conversion, multi-engine web search, code content searching, and system information retrieval.
3
+ Version: 0.1.31
4
+ Summary: This MCP server provides a comprehensive set of tools for AI-driven Development workflows including file operations, code analysis, code linting, multi-language execution, Git operations, web content fetching with HTML-to-markdown conversion, multi-engine web search, code content searching, and system information retrieval.
5
5
  Project-URL: Homepage, https://github.com/skydeckai/skydeckai-code
6
6
  Project-URL: Repository, https://github.com/skydeckai/skydeckai-code
7
7
  Project-URL: Documentation, https://github.com/skydeckai/skydeckai-code/blob/main/README.md
@@ -36,7 +36,7 @@ Description-Content-Type: text/markdown
36
36
 
37
37
  # SkyDeckAI Code
38
38
 
39
- An MCP server that provides a comprehensive set of tools for AI-driven development workflows. Features include file system operations, code analysis using tree-sitter for multiple programming languages, Git operations, code execution, web content fetching with HTML-to-markdown conversion, multi-engine web search, code content searching, and system information retrieval. Designed to enhance AI's capability to assist in software development tasks by providing direct access to both local and remote resources.
39
+ An MCP server that provides a comprehensive set of tools for AI-driven development workflows. Features include file system operations, code analysis using tree-sitter for multiple programming languages, Git operations, code execution, web content fetching with HTML-to-markdown conversion, multi-engine web search, code content searching, linting detection, and system information retrieval. Designed to enhance AI's capability to assist in software development tasks by providing direct access to both local and remote resources.
40
40
 
41
41
  # Formerly Known As MCP-Server-AIDD
42
42
 
@@ -77,6 +77,7 @@ If you're using SkyDeck AI Helper app, you can search for "SkyDeckAI Code" and i
77
77
  - File system operations (read, write, edit, move, copy, delete)
78
78
  - Directory management and traversal
79
79
  - Multi-language code analysis using tree-sitter
80
+ - Code linting and issue detection for Python and JavaScript/TypeScript
80
81
  - Code content searching with regex pattern matching
81
82
  - Multi-language code execution with safety measures
82
83
  - Git operations (status, diff, commit, branch management, cloning)
@@ -140,8 +141,6 @@ Pattern-based file editing with preview support:
140
141
  ],
141
142
  "dryRun": false,
142
143
  "options": {
143
- "preserveIndentation": true,
144
- "normalizeWhitespace": true,
145
144
  "partialMatch": true
146
145
  }
147
146
  }
@@ -281,6 +280,73 @@ Supported Languages:
281
280
  - C# (.cs)
282
281
  - Kotlin (.kt, .kts)
283
282
 
283
+ #### check_lint
284
+
285
+ Check for linting issues in your codebase using native linting tools:
286
+
287
+ ```json
288
+ {
289
+ "path": "src",
290
+ "languages": ["python", "javascript"],
291
+ "linters": {
292
+ "pylint": "--disable=C0111",
293
+ "flake8": true,
294
+ "eslint": "--fix"
295
+ },
296
+ "max_issues": 100
297
+ }
298
+ ```
299
+
300
+ **Parameters:**
301
+ | Parameter | Type | Required | Description |
302
+ |-----------|------|----------|-------------|
303
+ | path | string | No | Directory or file to lint (default: ".") |
304
+ | languages | array | No | List of languages to lint (auto-detects if empty) |
305
+ | linters | object | No | Configuration for specific linters - can use booleans or CLI arguments |
306
+ | max_issues | integer | No | Maximum number of issues to return (default: 100, 0 for unlimited) |
307
+
308
+ **Returns:**
309
+ A detailed report of linting issues found in the codebase, including file paths, line numbers, issue descriptions, and severity levels. Issues are grouped by file and sorted by severity.
310
+
311
+ **Supported Languages and Linters:**
312
+
313
+ - Python: pylint, flake8 (automatically uses what's available)
314
+ - JavaScript/TypeScript: ESLint
315
+ - Dart/Flutter: dart_analyze (also reports compilation errors)
316
+
317
+ **Example Usage:**
318
+
319
+ ```bash
320
+ # Check entire codebase with default settings
321
+ skydeckai-code-cli --tool check_lint
322
+
323
+ # Check specific directory with custom pylint flags
324
+ skydeckai-code-cli --tool check_lint --args '{
325
+ "path": "src",
326
+ "linters": {
327
+ "pylint": "--disable=missing-docstring,invalid-name"
328
+ }
329
+ }'
330
+
331
+ # Check only Python files and disable flake8
332
+ skydeckai-code-cli --tool check_lint --args '{
333
+ "path": "src",
334
+ "languages": ["python"],
335
+ "linters": {
336
+ "flake8": false
337
+ }
338
+ }'
339
+
340
+ # Check Dart/Flutter files for linting and compilation errors
341
+ skydeckai-code-cli --tool check_lint --args '{
342
+ "path": "lib",
343
+ "languages": ["dart"],
344
+ "linters": {
345
+ "dart_analyze": "--fatal-infos"
346
+ }
347
+ }'
348
+ ```
349
+
284
350
  #### search_code
285
351
 
286
352
  Fast content search tool using regular expressions:
@@ -1,25 +1,26 @@
1
1
  aidd/__init__.py,sha256=c9HBWxWruCxoAqLCJqltylAwz_7xmaK3g8DKViJZs0Q,222
2
2
  aidd/cli.py,sha256=cLtaQJmMBfr7fHkd0dyJqpDrVTIwybL48PotniWGrFM,5031
3
3
  aidd/server.py,sha256=kPRyWeWkMCZjabelC65XTmzZG7yw8htMJKSfnUcKnb0,1575
4
- aidd/tools/__init__.py,sha256=Oyl9YzMB1SRT_skxMcWHPP7ScTjfBSgT3N4ctGxHMAI,5310
4
+ aidd/tools/__init__.py,sha256=abFameL2CdSZohi7Sa1tUEOzD2M6bcXSOkRpgxfZ1mo,5429
5
5
  aidd/tools/base.py,sha256=wHSAaGGYWM8ECmoYd7KEcmjsZRWesNQFf3zMjCKGMcc,380
6
6
  aidd/tools/code_analysis.py,sha256=fDpm2o_If5PsngXzHN2-ezSkPVT0ZxivLuzmHrOAmVU,33188
7
7
  aidd/tools/code_execution.py,sha256=dIPxHBtclsetDZY4jGlSBrw_t-7VlIVrK8mflnZ6c4w,13176
8
- aidd/tools/code_tools.py,sha256=rkKPtChLWnlMcluDQDAUxWM8sCkfodDerx5JbU8mUN8,12616
8
+ aidd/tools/code_tools.py,sha256=3CgkQ78iVKMd5j8aLmolLp4c59seD42Qw6VbdUcg2wA,12628
9
9
  aidd/tools/directory_tools.py,sha256=Hxzge_ziYw_FsjYb5yF0R0dHEdvuWRsg7WsdYDG0AUg,12971
10
- aidd/tools/file_tools.py,sha256=IDgGr0EyLEEGmbsy-L1gnf7QbR2QaEi8KeX7NJPe5Zo,43823
10
+ aidd/tools/file_tools.py,sha256=jH1-c9sEqTPaUy1edvKIDVMfLs0osC7Xa8Mp94cICfo,42490
11
11
  aidd/tools/get_active_apps_tool.py,sha256=BjLF7iXSDgyAmm_gfFgAul2Gn3iX-CNVYHM7Sh4jTAI,19427
12
12
  aidd/tools/get_available_windows_tool.py,sha256=OVIYhItTn9u_DftOr3vPCT-R0DOFvMEEJXA6tD6gqWQ,15952
13
13
  aidd/tools/git_tools.py,sha256=AgolgrZnpN2NALV7SfIwc6D7U7tdPrPTSFmU2WjPfVE,39846
14
14
  aidd/tools/image_tools.py,sha256=wT3EcJAfZWcM0IsXdDfbTNjgFhKZM9nu2wHN6Mk_TTQ,5970
15
+ aidd/tools/lint_tools.py,sha256=0RYE-cXSbfw1VV_03GiFgYhC9ElhdWc4ecEjfMd9Els,25831
15
16
  aidd/tools/other_tools.py,sha256=iG3Sd2FP0M0pRv5esPBAUMvlwxTyAMDUdS77IqA_f5s,10822
16
17
  aidd/tools/path_tools.py,sha256=RGoOhqP69eHJzM8tEgn_5-GRaR0gp25fd0XZIJ_RnQE,4045
17
18
  aidd/tools/screenshot_tool.py,sha256=NMO5B4UG8qfMEOMRd2YoOjtwz_oQ2y1UAGU22jV1yGU,46337
18
19
  aidd/tools/state.py,sha256=RWSw0Jfsui8FqC0xsI7Ik07tAg35hRwLHa5xGBVbiI4,1493
19
20
  aidd/tools/system_tools.py,sha256=H4_qveKC2HA7SIbi-j4vxA0W4jYh2wfu9A6ni5wkZyA,7249
20
21
  aidd/tools/web_tools.py,sha256=gdsj2DEVYb_oYChItK5I1ugt2w25U7IAa5kEw9q6MVg,35534
21
- skydeckai_code-0.1.29.dist-info/METADATA,sha256=Dud6g3fMZkW-mZtdK0zyT6DjF4BvboiYgWEKrzYmjzY,30926
22
- skydeckai_code-0.1.29.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
23
- skydeckai_code-0.1.29.dist-info/entry_points.txt,sha256=cT-IHh3_ioGLk3kwIeqj1X6Li1dnJinX9qKWUl7nOLg,80
24
- skydeckai_code-0.1.29.dist-info/licenses/LICENSE,sha256=uHse04vmI6ZjW7TblegFl30X-sDyyF0-QvH8ItPca3c,10865
25
- skydeckai_code-0.1.29.dist-info/RECORD,,
22
+ skydeckai_code-0.1.31.dist-info/METADATA,sha256=117gd_kdJubtWEyNNMXBiUpsj-puKOspg9UeIWeMGVo,32850
23
+ skydeckai_code-0.1.31.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
24
+ skydeckai_code-0.1.31.dist-info/entry_points.txt,sha256=cT-IHh3_ioGLk3kwIeqj1X6Li1dnJinX9qKWUl7nOLg,80
25
+ skydeckai_code-0.1.31.dist-info/licenses/LICENSE,sha256=uHse04vmI6ZjW7TblegFl30X-sDyyF0-QvH8ItPca3c,10865
26
+ skydeckai_code-0.1.31.dist-info/RECORD,,