lintro 0.13.2__py3-none-any.whl → 0.17.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. lintro/__init__.py +1 -1
  2. lintro/cli.py +226 -16
  3. lintro/cli_utils/commands/__init__.py +8 -1
  4. lintro/cli_utils/commands/check.py +1 -0
  5. lintro/cli_utils/commands/config.py +325 -0
  6. lintro/cli_utils/commands/init.py +361 -0
  7. lintro/cli_utils/commands/list_tools.py +180 -42
  8. lintro/cli_utils/commands/test.py +316 -0
  9. lintro/cli_utils/commands/versions.py +81 -0
  10. lintro/config/__init__.py +62 -0
  11. lintro/config/config_loader.py +420 -0
  12. lintro/config/lintro_config.py +189 -0
  13. lintro/config/tool_config_generator.py +403 -0
  14. lintro/enums/tool_name.py +2 -0
  15. lintro/enums/tool_type.py +2 -0
  16. lintro/formatters/tools/__init__.py +12 -0
  17. lintro/formatters/tools/eslint_formatter.py +108 -0
  18. lintro/formatters/tools/markdownlint_formatter.py +88 -0
  19. lintro/formatters/tools/pytest_formatter.py +201 -0
  20. lintro/parsers/__init__.py +69 -9
  21. lintro/parsers/bandit/__init__.py +6 -0
  22. lintro/parsers/bandit/bandit_issue.py +49 -0
  23. lintro/parsers/bandit/bandit_parser.py +99 -0
  24. lintro/parsers/black/black_issue.py +4 -0
  25. lintro/parsers/eslint/__init__.py +6 -0
  26. lintro/parsers/eslint/eslint_issue.py +26 -0
  27. lintro/parsers/eslint/eslint_parser.py +63 -0
  28. lintro/parsers/markdownlint/__init__.py +6 -0
  29. lintro/parsers/markdownlint/markdownlint_issue.py +22 -0
  30. lintro/parsers/markdownlint/markdownlint_parser.py +113 -0
  31. lintro/parsers/pytest/__init__.py +21 -0
  32. lintro/parsers/pytest/pytest_issue.py +28 -0
  33. lintro/parsers/pytest/pytest_parser.py +483 -0
  34. lintro/tools/__init__.py +2 -0
  35. lintro/tools/core/timeout_utils.py +112 -0
  36. lintro/tools/core/tool_base.py +255 -45
  37. lintro/tools/core/tool_manager.py +77 -24
  38. lintro/tools/core/version_requirements.py +482 -0
  39. lintro/tools/implementations/pytest/pytest_command_builder.py +311 -0
  40. lintro/tools/implementations/pytest/pytest_config.py +200 -0
  41. lintro/tools/implementations/pytest/pytest_error_handler.py +128 -0
  42. lintro/tools/implementations/pytest/pytest_executor.py +122 -0
  43. lintro/tools/implementations/pytest/pytest_handlers.py +375 -0
  44. lintro/tools/implementations/pytest/pytest_option_validators.py +212 -0
  45. lintro/tools/implementations/pytest/pytest_output_processor.py +408 -0
  46. lintro/tools/implementations/pytest/pytest_result_processor.py +113 -0
  47. lintro/tools/implementations/pytest/pytest_utils.py +697 -0
  48. lintro/tools/implementations/tool_actionlint.py +106 -16
  49. lintro/tools/implementations/tool_bandit.py +23 -7
  50. lintro/tools/implementations/tool_black.py +236 -29
  51. lintro/tools/implementations/tool_darglint.py +180 -21
  52. lintro/tools/implementations/tool_eslint.py +374 -0
  53. lintro/tools/implementations/tool_hadolint.py +94 -25
  54. lintro/tools/implementations/tool_markdownlint.py +354 -0
  55. lintro/tools/implementations/tool_prettier.py +313 -26
  56. lintro/tools/implementations/tool_pytest.py +327 -0
  57. lintro/tools/implementations/tool_ruff.py +247 -70
  58. lintro/tools/implementations/tool_yamllint.py +448 -34
  59. lintro/tools/tool_enum.py +6 -0
  60. lintro/utils/config.py +41 -18
  61. lintro/utils/console_logger.py +211 -25
  62. lintro/utils/path_utils.py +42 -0
  63. lintro/utils/tool_executor.py +336 -39
  64. lintro/utils/tool_utils.py +38 -2
  65. lintro/utils/unified_config.py +926 -0
  66. {lintro-0.13.2.dist-info → lintro-0.17.2.dist-info}/METADATA +131 -29
  67. lintro-0.17.2.dist-info/RECORD +134 -0
  68. lintro-0.13.2.dist-info/RECORD +0 -96
  69. {lintro-0.13.2.dist-info → lintro-0.17.2.dist-info}/WHEEL +0 -0
  70. {lintro-0.13.2.dist-info → lintro-0.17.2.dist-info}/entry_points.txt +0 -0
  71. {lintro-0.13.2.dist-info → lintro-0.17.2.dist-info}/licenses/LICENSE +0 -0
  72. {lintro-0.13.2.dist-info → lintro-0.17.2.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,7 @@
1
1
  """Ruff Python linter and formatter integration."""
2
2
 
3
3
  import os
4
+ import subprocess # nosec B404 - used safely with shell disabled
4
5
  import tomllib
5
6
  from dataclasses import dataclass, field
6
7
  from pathlib import Path
@@ -14,6 +15,11 @@ from lintro.parsers.ruff.ruff_parser import (
14
15
  parse_ruff_format_check_output,
15
16
  parse_ruff_output,
16
17
  )
18
+ from lintro.tools.core.timeout_utils import (
19
+ create_timeout_result,
20
+ get_timeout_value,
21
+ run_subprocess_with_timeout,
22
+ )
17
23
  from lintro.tools.core.tool_base import BaseTool
18
24
  from lintro.utils.tool_utils import walk_files_with_excludes
19
25
 
@@ -31,7 +37,7 @@ def _load_ruff_config() -> dict:
31
37
  """Load ruff configuration from pyproject.toml.
32
38
 
33
39
  Returns:
34
- dict: Ruff configuration dictionary.
40
+ dict: Ruff configuration dictionary with flattened lint settings.
35
41
  """
36
42
  config: dict = {}
37
43
  pyproject_path = Path("pyproject.toml")
@@ -41,7 +47,20 @@ def _load_ruff_config() -> dict:
41
47
  with open(pyproject_path, "rb") as f:
42
48
  pyproject_data = tomllib.load(f)
43
49
  if "tool" in pyproject_data and "ruff" in pyproject_data["tool"]:
44
- config = pyproject_data["tool"]["ruff"]
50
+ ruff_config = pyproject_data["tool"]["ruff"]
51
+ # Copy top-level settings
52
+ config = dict(ruff_config)
53
+ # Flatten nested lint section to top level for easy access
54
+ if "lint" in ruff_config:
55
+ lint_config = ruff_config["lint"]
56
+ if "select" in lint_config:
57
+ config["select"] = lint_config["select"]
58
+ if "ignore" in lint_config:
59
+ config["ignore"] = lint_config["ignore"]
60
+ if "extend-select" in lint_config:
61
+ config["extend_select"] = lint_config["extend-select"]
62
+ if "extend-ignore" in lint_config:
63
+ config["extend_ignore"] = lint_config["extend-ignore"]
45
64
  except Exception as e:
46
65
  logger.warning(f"Failed to load ruff configuration: {e}")
47
66
 
@@ -54,10 +73,12 @@ def _load_lintro_ignore() -> list[str]:
54
73
  Returns:
55
74
  list[str]: List of ignore patterns.
56
75
  """
76
+ from lintro.utils.path_utils import find_lintro_ignore
77
+
57
78
  ignore_patterns: list[str] = []
58
- lintro_ignore_path = Path(".lintro-ignore")
79
+ lintro_ignore_path = find_lintro_ignore()
59
80
 
60
- if lintro_ignore_path.exists():
81
+ if lintro_ignore_path and lintro_ignore_path.exists():
61
82
  try:
62
83
  with open(lintro_ignore_path, encoding="utf-8") as f:
63
84
  for line in f:
@@ -128,29 +149,32 @@ class RuffTool(BaseTool):
128
149
  """Initialize the tool with default configuration."""
129
150
  super().__post_init__()
130
151
 
131
- # Load ruff configuration from pyproject.toml
132
- ruff_config = _load_ruff_config()
133
-
134
- # Load .lintro-ignore patterns
135
- lintro_ignore_patterns = _load_lintro_ignore()
136
-
137
- # Update exclude patterns from configuration and .lintro-ignore
138
- if "exclude" in ruff_config:
139
- self.exclude_patterns.extend(ruff_config["exclude"])
140
- if lintro_ignore_patterns:
141
- self.exclude_patterns.extend(lintro_ignore_patterns)
142
-
143
- # Update other options from configuration
144
- if "line_length" in ruff_config:
145
- self.options["line_length"] = ruff_config["line_length"]
146
- if "target_version" in ruff_config:
147
- self.options["target_version"] = ruff_config["target_version"]
148
- if "select" in ruff_config:
149
- self.options["select"] = ruff_config["select"]
150
- if "ignore" in ruff_config:
151
- self.options["ignore"] = ruff_config["ignore"]
152
- if "unsafe_fixes" in ruff_config:
153
- self.options["unsafe_fixes"] = ruff_config["unsafe_fixes"]
152
+ # Skip config loading in test mode to allow tests to set specific options
153
+ # without interference from pyproject.toml settings
154
+ if os.environ.get(RUFF_TEST_MODE_ENV) != RUFF_TEST_MODE_VALUE:
155
+ # Load ruff configuration from pyproject.toml
156
+ ruff_config = _load_ruff_config()
157
+
158
+ # Load .lintro-ignore patterns
159
+ lintro_ignore_patterns = _load_lintro_ignore()
160
+
161
+ # Update exclude patterns from configuration and .lintro-ignore
162
+ if "exclude" in ruff_config:
163
+ self.exclude_patterns.extend(ruff_config["exclude"])
164
+ if lintro_ignore_patterns:
165
+ self.exclude_patterns.extend(lintro_ignore_patterns)
166
+
167
+ # Update other options from configuration
168
+ if "line_length" in ruff_config:
169
+ self.options["line_length"] = ruff_config["line_length"]
170
+ if "target_version" in ruff_config:
171
+ self.options["target_version"] = ruff_config["target_version"]
172
+ if "select" in ruff_config:
173
+ self.options["select"] = ruff_config["select"]
174
+ if "ignore" in ruff_config:
175
+ self.options["ignore"] = ruff_config["ignore"]
176
+ if "unsafe_fixes" in ruff_config:
177
+ self.options["unsafe_fixes"] = ruff_config["unsafe_fixes"]
154
178
 
155
179
  # Allow environment variable override for unsafe fixes
156
180
  # Useful for development and CI environments
@@ -267,8 +291,16 @@ class RuffTool(BaseTool):
267
291
  """
268
292
  cmd: list[str] = self._get_executable_command(tool_name="ruff") + ["check"]
269
293
 
270
- # Add --isolated if in test mode
271
- if os.environ.get(RUFF_TEST_MODE_ENV) == RUFF_TEST_MODE_VALUE:
294
+ # Get enforced settings to avoid duplicate CLI args
295
+ enforced = self._get_enforced_settings()
296
+
297
+ # Add Lintro config injection args (--line-length, --target-version)
298
+ # from enforce tier. This takes precedence over native config and options
299
+ config_args = self._build_config_args()
300
+ if config_args:
301
+ cmd.extend(config_args)
302
+ # Add --isolated if in test mode (fallback when no Lintro config)
303
+ elif os.environ.get(RUFF_TEST_MODE_ENV) == RUFF_TEST_MODE_VALUE:
272
304
  cmd.append("--isolated")
273
305
 
274
306
  # Add configuration options
@@ -291,11 +323,15 @@ class RuffTool(BaseTool):
291
323
  cmd.extend(["--ignore", ",".join(sorted(ignored_rules))])
292
324
  if extend_selected_rules:
293
325
  cmd.extend(["--extend-select", ",".join(extend_selected_rules)])
294
- if self.options.get("extend_ignore"):
295
- cmd.extend(["--extend-ignore", ",".join(self.options["extend_ignore"])])
296
- if self.options.get("line_length"):
326
+ extend_ignored_rules = list(self.options.get("extend_ignore") or [])
327
+ if extend_ignored_rules:
328
+ cmd.extend(["--extend-ignore", ",".join(extend_ignored_rules)])
329
+ # Only add line_length/target_version from options if not enforced.
330
+ # Note: enforced uses Lintro's generic names (line_length, target_python)
331
+ # while options use tool-specific names (line_length, target_version).
332
+ if self.options.get("line_length") and "line_length" not in enforced:
297
333
  cmd.extend(["--line-length", str(self.options["line_length"])])
298
- if self.options.get("target_version"):
334
+ if self.options.get("target_version") and "target_python" not in enforced:
299
335
  cmd.extend(["--target-version", self.options["target_version"]])
300
336
 
301
337
  # Fix options
@@ -335,11 +371,16 @@ class RuffTool(BaseTool):
335
371
  if check_only:
336
372
  cmd.append("--check")
337
373
 
338
- # Add configuration options
339
- if self.options.get("line_length"):
340
- cmd.extend(["--line-length", str(self.options["line_length"])])
341
- if self.options.get("target_version"):
342
- cmd.extend(["--target-version", self.options["target_version"]])
374
+ # Add Lintro config injection args (--isolated, --config)
375
+ config_args = self._build_config_args()
376
+ if config_args:
377
+ cmd.extend(config_args)
378
+ else:
379
+ # Fallback to options-based configuration
380
+ if self.options.get("line_length"):
381
+ cmd.extend(["--line-length", str(self.options["line_length"])])
382
+ if self.options.get("target_version"):
383
+ cmd.extend(["--target-version", self.options["target_version"]])
343
384
 
344
385
  # Add files
345
386
  cmd.extend(files)
@@ -358,6 +399,11 @@ class RuffTool(BaseTool):
358
399
  Returns:
359
400
  ToolResult: ToolResult instance.
360
401
  """
402
+ # Check version requirements
403
+ version_result = self._verify_tool_version()
404
+ if version_result is not None:
405
+ return version_result
406
+
361
407
  self._validate_paths(paths=paths)
362
408
  if not paths:
363
409
  return ToolResult(
@@ -393,16 +439,31 @@ class RuffTool(BaseTool):
393
439
  os.path.relpath(f, cwd) if cwd else f for f in python_files
394
440
  ]
395
441
 
396
- timeout: int = self.options.get("timeout", RUFF_DEFAULT_TIMEOUT)
442
+ timeout: int = get_timeout_value(self, RUFF_DEFAULT_TIMEOUT)
397
443
  # Lint check
398
444
  cmd: list[str] = self._build_check_command(files=rel_files, fix=False)
399
445
  success_lint: bool
400
446
  output_lint: str
401
- success_lint, output_lint = self._run_subprocess(
402
- cmd=cmd,
403
- timeout=timeout,
404
- cwd=cwd,
405
- )
447
+ try:
448
+ success_lint, output_lint = run_subprocess_with_timeout(
449
+ tool=self,
450
+ cmd=cmd,
451
+ timeout=timeout,
452
+ cwd=cwd,
453
+ )
454
+ except subprocess.TimeoutExpired:
455
+ timeout_result = create_timeout_result(
456
+ tool=self,
457
+ timeout=timeout,
458
+ cmd=cmd,
459
+ )
460
+ return ToolResult(
461
+ name=self.name,
462
+ success=timeout_result["success"],
463
+ output=timeout_result["output"],
464
+ issues_count=timeout_result["issues_count"],
465
+ issues=timeout_result["issues"],
466
+ )
406
467
  lint_issues = parse_ruff_output(output=output_lint)
407
468
  lint_issues_count: int = len(lint_issues)
408
469
 
@@ -417,11 +478,26 @@ class RuffTool(BaseTool):
417
478
  )
418
479
  success_format: bool
419
480
  output_format: str
420
- success_format, output_format = self._run_subprocess(
421
- cmd=format_cmd,
422
- timeout=timeout,
423
- cwd=cwd,
424
- )
481
+ try:
482
+ success_format, output_format = self._run_subprocess(
483
+ cmd=format_cmd,
484
+ timeout=timeout,
485
+ cwd=cwd,
486
+ )
487
+ except subprocess.TimeoutExpired:
488
+ timeout_msg = (
489
+ f"Ruff execution timed out ({timeout}s limit exceeded).\n\n"
490
+ "This may indicate:\n"
491
+ " - Large codebase taking too long to process\n"
492
+ " - Need to increase timeout via --tool-options ruff:timeout=N"
493
+ )
494
+ return ToolResult(
495
+ name=self.name,
496
+ success=False,
497
+ output=timeout_msg,
498
+ issues_count=1, # Count timeout as execution failure
499
+ issues=lint_issues, # Include any lint issues found before timeout
500
+ )
425
501
  format_files = parse_ruff_format_check_output(output=output_format)
426
502
  # Normalize files to absolute paths to keep behavior consistent with
427
503
  # direct CLI calls and stabilize tests that compare exact paths.
@@ -465,6 +541,11 @@ class RuffTool(BaseTool):
465
541
  Returns:
466
542
  ToolResult: ToolResult instance.
467
543
  """
544
+ # Check version requirements
545
+ version_result = self._verify_tool_version()
546
+ if version_result is not None:
547
+ return version_result
548
+
468
549
  self._validate_paths(paths=paths)
469
550
  if not paths:
470
551
  return ToolResult(
@@ -491,7 +572,7 @@ class RuffTool(BaseTool):
491
572
  )
492
573
 
493
574
  logger.debug(f"Files to fix: {python_files}")
494
- timeout: int = self.options.get("timeout", RUFF_DEFAULT_TIMEOUT)
575
+ timeout: int = get_timeout_value(self, RUFF_DEFAULT_TIMEOUT)
495
576
  all_outputs: list[str] = []
496
577
  overall_success: bool = True
497
578
 
@@ -502,10 +583,28 @@ class RuffTool(BaseTool):
502
583
  cmd_check: list[str] = self._build_check_command(files=python_files, fix=False)
503
584
  success_check: bool
504
585
  output_check: str
505
- success_check, output_check = self._run_subprocess(
506
- cmd=cmd_check,
507
- timeout=timeout,
508
- )
586
+ try:
587
+ success_check, output_check = run_subprocess_with_timeout(
588
+ tool=self,
589
+ cmd=cmd_check,
590
+ timeout=timeout,
591
+ )
592
+ except subprocess.TimeoutExpired:
593
+ timeout_result = create_timeout_result(
594
+ tool=self,
595
+ timeout=timeout,
596
+ cmd=cmd_check,
597
+ )
598
+ return ToolResult(
599
+ name=self.name,
600
+ success=timeout_result["success"],
601
+ output=timeout_result["output"],
602
+ issues_count=timeout_result["issues_count"],
603
+ issues=timeout_result["issues"],
604
+ initial_issues_count=0,
605
+ fixed_issues_count=0,
606
+ remaining_issues_count=1,
607
+ )
509
608
  initial_issues = parse_ruff_output(output=output_check)
510
609
  initial_count: int = len(initial_issues)
511
610
 
@@ -519,10 +618,29 @@ class RuffTool(BaseTool):
519
618
  )
520
619
  success_format_check: bool
521
620
  output_format_check: str
522
- success_format_check, output_format_check = self._run_subprocess(
523
- cmd=format_cmd_check,
524
- timeout=timeout,
525
- )
621
+ try:
622
+ success_format_check, output_format_check = self._run_subprocess(
623
+ cmd=format_cmd_check,
624
+ timeout=timeout,
625
+ )
626
+ except subprocess.TimeoutExpired:
627
+ timeout_msg = (
628
+ f"Ruff execution timed out ({timeout}s limit exceeded).\n\n"
629
+ "This may indicate:\n"
630
+ " - Large codebase taking too long to process\n"
631
+ " - Need to increase timeout via --tool-options ruff:timeout=N"
632
+ )
633
+ return ToolResult(
634
+ name=self.name,
635
+ success=False,
636
+ output=timeout_msg,
637
+ issues_count=1, # Count timeout as execution failure
638
+ # Include any lint issues found before timeout
639
+ issues=initial_issues,
640
+ initial_issues_count=initial_count,
641
+ fixed_issues_count=0,
642
+ remaining_issues_count=1,
643
+ )
526
644
  format_files = parse_ruff_format_check_output(output=output_format_check)
527
645
  initial_format_count = len(format_files)
528
646
 
@@ -536,7 +654,25 @@ class RuffTool(BaseTool):
536
654
  if self.options.get("lint_fix", True):
537
655
  cmd: list[str] = self._build_check_command(files=python_files, fix=True)
538
656
  output: str
539
- success, output = self._run_subprocess(cmd=cmd, timeout=timeout)
657
+ try:
658
+ success, output = self._run_subprocess(cmd=cmd, timeout=timeout)
659
+ except subprocess.TimeoutExpired:
660
+ timeout_msg = (
661
+ f"Ruff execution timed out ({timeout}s limit exceeded).\n\n"
662
+ "This may indicate:\n"
663
+ " - Large codebase taking too long to process\n"
664
+ " - Need to increase timeout via --tool-options ruff:timeout=N"
665
+ )
666
+ return ToolResult(
667
+ name=self.name,
668
+ success=False,
669
+ output=timeout_msg,
670
+ issues_count=1, # Count timeout as execution failure
671
+ issues=initial_issues, # Include initial issues found
672
+ initial_issues_count=total_initial_count,
673
+ fixed_issues_count=0,
674
+ remaining_issues_count=1,
675
+ )
540
676
  remaining_issues = parse_ruff_output(output=output)
541
677
  remaining_count = len(remaining_issues)
542
678
 
@@ -567,11 +703,18 @@ class RuffTool(BaseTool):
567
703
  # Only run if not already run with unsafe fixes
568
704
  success_unsafe: bool
569
705
  output_unsafe: str
570
- success_unsafe, output_unsafe = self._run_subprocess(
571
- cmd=cmd_unsafe,
572
- timeout=timeout,
573
- )
574
- remaining_unsafe = parse_ruff_output(output=output_unsafe)
706
+ try:
707
+ success_unsafe, output_unsafe = self._run_subprocess(
708
+ cmd=cmd_unsafe,
709
+ timeout=timeout,
710
+ )
711
+ except subprocess.TimeoutExpired:
712
+ # If unsafe check times out, just continue with current results
713
+ # Don't fail the entire operation for this optional check
714
+ logger.debug("Unsafe fixes check timed out, skipping")
715
+ remaining_unsafe = remaining_issues
716
+ else:
717
+ remaining_unsafe = parse_ruff_output(output=output_unsafe)
575
718
  if len(remaining_unsafe) < remaining_count:
576
719
  all_outputs.append(
577
720
  "Some remaining issues could be fixed by enabling unsafe "
@@ -613,10 +756,28 @@ class RuffTool(BaseTool):
613
756
  )
614
757
  format_success: bool
615
758
  format_output: str
616
- format_success, format_output = self._run_subprocess(
617
- cmd=format_cmd,
618
- timeout=timeout,
619
- )
759
+ try:
760
+ format_success, format_output = self._run_subprocess(
761
+ cmd=format_cmd,
762
+ timeout=timeout,
763
+ )
764
+ except subprocess.TimeoutExpired:
765
+ timeout_msg = (
766
+ f"Ruff execution timed out ({timeout}s limit exceeded).\n\n"
767
+ "This may indicate:\n"
768
+ " - Large codebase taking too long to process\n"
769
+ " - Need to increase timeout via --tool-options ruff:timeout=N"
770
+ )
771
+ return ToolResult(
772
+ name=self.name,
773
+ success=False,
774
+ output=timeout_msg,
775
+ issues_count=1, # Count timeout as execution failure
776
+ issues=remaining_issues, # Include any issues found before timeout
777
+ initial_issues_count=total_initial_count,
778
+ fixed_issues_count=fixed_lint_count,
779
+ remaining_issues_count=1,
780
+ )
620
781
  # Formatting fixes are counted separately from lint fixes
621
782
  if initial_format_count > 0:
622
783
  fixed_count = fixed_lint_count + initial_format_count
@@ -643,14 +804,30 @@ class RuffTool(BaseTool):
643
804
  # If there are no initial issues, success should be True
644
805
  overall_success = True if total_initial_count == 0 else remaining_count == 0
645
806
 
807
+ # Convert initial format files to RuffFormatIssue objects (these were fixed)
808
+ # and combine with remaining issues so formatter can split them by fixability
809
+ fixed_format_issues: list[RuffFormatIssue] = []
810
+ if format_files:
811
+ # Normalize files to absolute paths to keep behavior consistent
812
+ cwd: str | None = self.get_cwd(paths=python_files)
813
+ for file_path in format_files:
814
+ if cwd and not os.path.isabs(file_path):
815
+ absolute_path = os.path.abspath(os.path.join(cwd, file_path))
816
+ fixed_format_issues.append(RuffFormatIssue(file=absolute_path))
817
+ else:
818
+ fixed_format_issues.append(RuffFormatIssue(file=file_path))
819
+
820
+ # Combine fixed format issues with remaining lint issues
821
+ all_issues = fixed_format_issues + remaining_issues
822
+
646
823
  return ToolResult(
647
824
  name=self.name,
648
825
  success=overall_success,
649
826
  output=final_output,
650
827
  # For fix operations, issues_count represents remaining for summaries
651
828
  issues_count=remaining_count,
652
- # Display remaining issues only to align tables with summary counts
653
- issues=remaining_issues,
829
+ # Display both fixed format issues and remaining lint issues
830
+ issues=all_issues,
654
831
  initial_issues_count=total_initial_count,
655
832
  fixed_issues_count=fixed_count,
656
833
  remaining_issues_count=remaining_count,