redup 0.4.26__tar.gz → 0.4.28__tar.gz

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 (112) hide show
  1. {redup-0.4.26/src/redup.egg-info → redup-0.4.28}/PKG-INFO +6 -6
  2. {redup-0.4.26 → redup-0.4.28}/README.md +5 -5
  3. {redup-0.4.26 → redup-0.4.28}/pyproject.toml +1 -1
  4. {redup-0.4.26 → redup-0.4.28}/src/redup/__init__.py +1 -1
  5. {redup-0.4.26 → redup-0.4.28}/src/redup/core/scanner_filters.py +43 -8
  6. {redup-0.4.26 → redup-0.4.28/src/redup.egg-info}/PKG-INFO +6 -6
  7. {redup-0.4.26 → redup-0.4.28}/tests/test_scanner.py +20 -0
  8. {redup-0.4.26 → redup-0.4.28}/LICENSE +0 -0
  9. {redup-0.4.26 → redup-0.4.28}/setup.cfg +0 -0
  10. {redup-0.4.26 → redup-0.4.28}/src/redup/__main__.py +0 -0
  11. {redup-0.4.26 → redup-0.4.28}/src/redup/analysis_logic.py +0 -0
  12. {redup-0.4.26 → redup-0.4.28}/src/redup/cli_app/__init__.py +0 -0
  13. {redup-0.4.26 → redup-0.4.28}/src/redup/cli_app/compare_command.py +0 -0
  14. {redup-0.4.26 → redup-0.4.28}/src/redup/cli_app/config_builder.py +0 -0
  15. {redup-0.4.26 → redup-0.4.28}/src/redup/cli_app/fuzzy_similarity.py +0 -0
  16. {redup-0.4.26 → redup-0.4.28}/src/redup/cli_app/main.py +0 -0
  17. {redup-0.4.26 → redup-0.4.28}/src/redup/cli_app/output_writer.py +0 -0
  18. {redup-0.4.26 → redup-0.4.28}/src/redup/cli_app/quality_commands.py +0 -0
  19. {redup-0.4.26 → redup-0.4.28}/src/redup/cli_app/scan_commands.py +0 -0
  20. {redup-0.4.26 → redup-0.4.28}/src/redup/cli_app/scan_helpers.py +0 -0
  21. {redup-0.4.26 → redup-0.4.28}/src/redup/cli_app/tasks_command.py +0 -0
  22. {redup-0.4.26 → redup-0.4.28}/src/redup/config.py +0 -0
  23. {redup-0.4.26 → redup-0.4.28}/src/redup/config_handler.py +0 -0
  24. {redup-0.4.26 → redup-0.4.28}/src/redup/core/__init__.py +0 -0
  25. {redup-0.4.26 → redup-0.4.28}/src/redup/core/cache.py +0 -0
  26. {redup-0.4.26 → redup-0.4.28}/src/redup/core/community.py +0 -0
  27. {redup-0.4.26 → redup-0.4.28}/src/redup/core/comparator.py +0 -0
  28. {redup-0.4.26 → redup-0.4.28}/src/redup/core/config.py +0 -0
  29. {redup-0.4.26 → redup-0.4.28}/src/redup/core/decision.py +0 -0
  30. {redup-0.4.26 → redup-0.4.28}/src/redup/core/differ.py +0 -0
  31. {redup-0.4.26 → redup-0.4.28}/src/redup/core/fuzzy_similarity.py +0 -0
  32. {redup-0.4.26 → redup-0.4.28}/src/redup/core/grouper.py +0 -0
  33. {redup-0.4.26 → redup-0.4.28}/src/redup/core/hash_cache.py +0 -0
  34. {redup-0.4.26 → redup-0.4.28}/src/redup/core/hasher.py +0 -0
  35. {redup-0.4.26 → redup-0.4.28}/src/redup/core/lazy_grouper.py +0 -0
  36. {redup-0.4.26 → redup-0.4.28}/src/redup/core/lsh_matcher.py +0 -0
  37. {redup-0.4.26 → redup-0.4.28}/src/redup/core/matcher.py +0 -0
  38. {redup-0.4.26 → redup-0.4.28}/src/redup/core/models.py +0 -0
  39. {redup-0.4.26 → redup-0.4.28}/src/redup/core/pipeline/__init__.py +0 -0
  40. {redup-0.4.26 → redup-0.4.28}/src/redup/core/pipeline/duplicate_finder.py +0 -0
  41. {redup-0.4.26 → redup-0.4.28}/src/redup/core/pipeline/groups.py +0 -0
  42. {redup-0.4.26 → redup-0.4.28}/src/redup/core/pipeline/phases.py +0 -0
  43. {redup-0.4.26 → redup-0.4.28}/src/redup/core/pipeline_utils.py +0 -0
  44. {redup-0.4.26 → redup-0.4.28}/src/redup/core/planner.py +0 -0
  45. {redup-0.4.26 → redup-0.4.28}/src/redup/core/python_parser.py +0 -0
  46. {redup-0.4.26 → redup-0.4.28}/src/redup/core/refactor_advisor.py +0 -0
  47. {redup-0.4.26 → redup-0.4.28}/src/redup/core/scanner/__init__.py +0 -0
  48. {redup-0.4.26 → redup-0.4.28}/src/redup/core/scanner.py +0 -0
  49. {redup-0.4.26 → redup-0.4.28}/src/redup/core/scanner_cache.py +0 -0
  50. {redup-0.4.26 → redup-0.4.28}/src/redup/core/scanner_loader.py +0 -0
  51. {redup-0.4.26 → redup-0.4.28}/src/redup/core/scanner_models.py +0 -0
  52. {redup-0.4.26 → redup-0.4.28}/src/redup/core/scanner_types.py +0 -0
  53. {redup-0.4.26 → redup-0.4.28}/src/redup/core/scanner_utils.py +0 -0
  54. {redup-0.4.26 → redup-0.4.28}/src/redup/core/semantic.py +0 -0
  55. {redup-0.4.26 → redup-0.4.28}/src/redup/core/ts_extractor/__init__.py +0 -0
  56. {redup-0.4.26 → redup-0.4.28}/src/redup/core/ts_extractor/config.py +0 -0
  57. {redup-0.4.26 → redup-0.4.28}/src/redup/core/ts_extractor/dispatcher.py +0 -0
  58. {redup-0.4.26 → redup-0.4.28}/src/redup/core/ts_extractor/extractors/__init__.py +0 -0
  59. {redup-0.4.26 → redup-0.4.28}/src/redup/core/ts_extractor/extractors/base.py +0 -0
  60. {redup-0.4.26 → redup-0.4.28}/src/redup/core/ts_extractor/extractors/c_family.py +0 -0
  61. {redup-0.4.26 → redup-0.4.28}/src/redup/core/ts_extractor/extractors/dotnet.py +0 -0
  62. {redup-0.4.26 → redup-0.4.28}/src/redup/core/ts_extractor/extractors/markup.py +0 -0
  63. {redup-0.4.26 → redup-0.4.28}/src/redup/core/ts_extractor/extractors/php.py +0 -0
  64. {redup-0.4.26 → redup-0.4.28}/src/redup/core/ts_extractor/extractors/query.py +0 -0
  65. {redup-0.4.26 → redup-0.4.28}/src/redup/core/ts_extractor/extractors/ruby.py +0 -0
  66. {redup-0.4.26 → redup-0.4.28}/src/redup/core/ts_extractor/extractors/shell.py +0 -0
  67. {redup-0.4.26 → redup-0.4.28}/src/redup/core/ts_extractor/extractors/stylesheet.py +0 -0
  68. {redup-0.4.26 → redup-0.4.28}/src/redup/core/ts_extractor/extractors/web.py +0 -0
  69. {redup-0.4.26 → redup-0.4.28}/src/redup/core/ts_extractor/main.py +0 -0
  70. {redup-0.4.26 → redup-0.4.28}/src/redup/core/universal_fuzzy.py +0 -0
  71. {redup-0.4.26 → redup-0.4.28}/src/redup/core/utils/__init__.py +0 -0
  72. {redup-0.4.26 → redup-0.4.28}/src/redup/core/utils/diff_helpers.py +0 -0
  73. {redup-0.4.26 → redup-0.4.28}/src/redup/core/utils/duplicate_finders.py +0 -0
  74. {redup-0.4.26 → redup-0.4.28}/src/redup/core/utils/function_extractor.py +0 -0
  75. {redup-0.4.26 → redup-0.4.28}/src/redup/core/utils/hash_utils.py +0 -0
  76. {redup-0.4.26 → redup-0.4.28}/src/redup/core/utils/language_dispatcher.py +0 -0
  77. {redup-0.4.26 → redup-0.4.28}/src/redup/integrations/__init__.py +0 -0
  78. {redup-0.4.26 → redup-0.4.28}/src/redup/integrations/planfile_integration.py +0 -0
  79. {redup-0.4.26 → redup-0.4.28}/src/redup/mcp/__init__.py +0 -0
  80. {redup-0.4.26 → redup-0.4.28}/src/redup/mcp/handlers.py +0 -0
  81. {redup-0.4.26 → redup-0.4.28}/src/redup/mcp/schemas.py +0 -0
  82. {redup-0.4.26 → redup-0.4.28}/src/redup/mcp/server.py +0 -0
  83. {redup-0.4.26 → redup-0.4.28}/src/redup/mcp/utils.py +0 -0
  84. {redup-0.4.26 → redup-0.4.28}/src/redup/mcp_server.py +0 -0
  85. {redup-0.4.26 → redup-0.4.28}/src/redup/mcp_server_clean.py +0 -0
  86. {redup-0.4.26 → redup-0.4.28}/src/redup/reporters/__init__.py +0 -0
  87. {redup-0.4.26 → redup-0.4.28}/src/redup/reporters/code2llm_reporter.py +0 -0
  88. {redup-0.4.26 → redup-0.4.28}/src/redup/reporters/enhanced_reporter.py +0 -0
  89. {redup-0.4.26 → redup-0.4.28}/src/redup/reporters/json_reporter.py +0 -0
  90. {redup-0.4.26 → redup-0.4.28}/src/redup/reporters/markdown_reporter.py +0 -0
  91. {redup-0.4.26 → redup-0.4.28}/src/redup/reporters/toon_reporter.py +0 -0
  92. {redup-0.4.26 → redup-0.4.28}/src/redup/reporters/yaml_reporter.py +0 -0
  93. {redup-0.4.26 → redup-0.4.28}/src/redup/reporters.py +0 -0
  94. {redup-0.4.26 → redup-0.4.28}/src/redup/utils.py +0 -0
  95. {redup-0.4.26 → redup-0.4.28}/src/redup.egg-info/SOURCES.txt +0 -0
  96. {redup-0.4.26 → redup-0.4.28}/src/redup.egg-info/dependency_links.txt +0 -0
  97. {redup-0.4.26 → redup-0.4.28}/src/redup.egg-info/entry_points.txt +0 -0
  98. {redup-0.4.26 → redup-0.4.28}/src/redup.egg-info/requires.txt +0 -0
  99. {redup-0.4.26 → redup-0.4.28}/src/redup.egg-info/top_level.txt +0 -0
  100. {redup-0.4.26 → redup-0.4.28}/tests/test_cli_import_compat.py +0 -0
  101. {redup-0.4.26 → redup-0.4.28}/tests/test_compare.py +0 -0
  102. {redup-0.4.26 → redup-0.4.28}/tests/test_e2e.py +0 -0
  103. {redup-0.4.26 → redup-0.4.28}/tests/test_hasher.py +0 -0
  104. {redup-0.4.26 → redup-0.4.28}/tests/test_matcher.py +0 -0
  105. {redup-0.4.26 → redup-0.4.28}/tests/test_mcp_server.py +0 -0
  106. {redup-0.4.26 → redup-0.4.28}/tests/test_models.py +0 -0
  107. {redup-0.4.26 → redup-0.4.28}/tests/test_pipeline.py +0 -0
  108. {redup-0.4.26 → redup-0.4.28}/tests/test_planfile_integration.py +0 -0
  109. {redup-0.4.26 → redup-0.4.28}/tests/test_planner.py +0 -0
  110. {redup-0.4.26 → redup-0.4.28}/tests/test_quality_commands.py +0 -0
  111. {redup-0.4.26 → redup-0.4.28}/tests/test_reporters.py +0 -0
  112. {redup-0.4.26 → redup-0.4.28}/tests/test_ts_extractor.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: redup
3
- Version: 0.4.26
3
+ Version: 0.4.28
4
4
  Summary: Code duplication analyzer and refactoring planner for LLMs
5
5
  Author-email: Tom Sapletta <tom@sapletta.com>
6
6
  License-Expression: Apache-2.0
@@ -107,16 +107,16 @@ Dynamic: license-file
107
107
  [![PyPI](https://img.shields.io/pypi/v/redup)](https://pypi.org/project/redup/)
108
108
  [![License: Apache-2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
109
109
  [![Python](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://python.org)
110
- [![Version](https://img.shields.io/badge/version-0.4.26-green.svg)](https://pypi.org/project/redup/)
110
+ [![Version](https://img.shields.io/badge/version-0.4.28-green.svg)](https://pypi.org/project/redup/)
111
111
 
112
112
 
113
113
  ## AI Cost Tracking
114
114
 
115
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.4.26-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
116
- ![AI Cost](https://img.shields.io/badge/AI%20Cost-$33.53-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-23.4h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
115
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.4.28-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
116
+ ![AI Cost](https://img.shields.io/badge/AI%20Cost-$33.81-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-24.6h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
117
117
 
118
- - 🤖 **LLM usage:** $33.5345 (70 commits)
119
- - 👤 **Human dev:** ~$2343 (23.4h @ $100/h, 30min dedup)
118
+ - 🤖 **LLM usage:** $33.8130 (71 commits)
119
+ - 👤 **Human dev:** ~$2459 (24.6h @ $100/h, 30min dedup)
120
120
 
121
121
  Generated on 2026-05-19 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
122
122
 
@@ -5,16 +5,16 @@
5
5
  [![PyPI](https://img.shields.io/pypi/v/redup)](https://pypi.org/project/redup/)
6
6
  [![License: Apache-2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
7
7
  [![Python](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://python.org)
8
- [![Version](https://img.shields.io/badge/version-0.4.26-green.svg)](https://pypi.org/project/redup/)
8
+ [![Version](https://img.shields.io/badge/version-0.4.28-green.svg)](https://pypi.org/project/redup/)
9
9
 
10
10
 
11
11
  ## AI Cost Tracking
12
12
 
13
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.4.26-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
14
- ![AI Cost](https://img.shields.io/badge/AI%20Cost-$33.53-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-23.4h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
13
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.4.28-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
14
+ ![AI Cost](https://img.shields.io/badge/AI%20Cost-$33.81-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-24.6h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
15
15
 
16
- - 🤖 **LLM usage:** $33.5345 (70 commits)
17
- - 👤 **Human dev:** ~$2343 (23.4h @ $100/h, 30min dedup)
16
+ - 🤖 **LLM usage:** $33.8130 (71 commits)
17
+ - 👤 **Human dev:** ~$2459 (24.6h @ $100/h, 30min dedup)
18
18
 
19
19
  Generated on 2026-05-19 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
20
20
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "redup"
7
- version = "0.4.26"
7
+ version = "0.4.28"
8
8
  description = "Code duplication analyzer and refactoring planner for LLMs"
9
9
  readme = "README.md"
10
10
  license = "Apache-2.0"
@@ -2,7 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- __version__ = "0.4.26"
5
+ __version__ = "0.4.28"
6
6
 
7
7
  # Click compatibility shim for older typer versions
8
8
  # This must run before any typer imports
@@ -29,21 +29,58 @@ def _is_test_file(path: Path) -> bool:
29
29
  return any("test" in part and "pytest-" not in part for part in dir_parts)
30
30
 
31
31
 
32
+ def _collect_target_files(config: ScanConfig) -> list[Path]:
33
+ """Collect an explicit target file list without walking the whole tree."""
34
+ if not config.target_files:
35
+ return []
36
+
37
+ files: list[Path] = []
38
+ seen: set[Path] = set()
39
+ ext_set = set(config.extensions)
40
+ exclude_patterns = tuple(config.exclude_patterns)
41
+
42
+ for target in config.target_files:
43
+ relative_path = Path(str(target)).as_posix().lstrip("./")
44
+ file_path = (config.root / relative_path).resolve()
45
+ try:
46
+ project_relative = _project_relative_path(file_path, config.root)
47
+ except ValueError:
48
+ continue
49
+
50
+ relative_posix = project_relative.as_posix().lstrip("./")
51
+ if relative_posix != relative_path:
52
+ continue
53
+ if file_path in seen or not file_path.is_file():
54
+ continue
55
+ if file_path.suffix not in ext_set:
56
+ continue
57
+ if _should_exclude(project_relative, exclude_patterns):
58
+ continue
59
+ if not config.include_tests and _is_test_file(project_relative):
60
+ continue
61
+ try:
62
+ if file_path.stat().st_size > config.max_file_size_kb * 1024:
63
+ continue
64
+ except OSError:
65
+ continue
66
+ seen.add(file_path)
67
+ files.append(file_path)
68
+
69
+ return files
70
+
71
+
32
72
  def _collect_files(config: ScanConfig) -> list[Path]:
33
73
  """Collect all files to scan based on configuration.
34
74
 
35
75
  Uses os.walk with topdown pruning to skip hidden directories early,
36
76
  avoiding descent into .rebuild_ev/, .regres/, etc.
37
77
  """
78
+ if config.target_files is not None:
79
+ return _collect_target_files(config)
80
+
38
81
  files = []
39
82
  ext_set = set(config.extensions)
40
83
  exclude_patterns = tuple(config.exclude_patterns)
41
- target_files = config.target_files
42
- target_set = (
43
- {Path(p).as_posix().lstrip("./") for p in target_files}
44
- if target_files
45
- else None
46
- )
47
84
  root_str = str(config.root)
48
85
 
49
86
  for dirpath, dirnames, filenames in os.walk(root_str, topdown=True):
@@ -55,8 +92,6 @@ def _collect_files(config: ScanConfig) -> list[Path]:
55
92
  continue
56
93
  relative_path = _project_relative_path(file_path, config.root)
57
94
  relative_posix = relative_path.as_posix().lstrip("./")
58
- if target_set is not None and relative_posix not in target_set:
59
- continue
60
95
  if _should_exclude(relative_path, exclude_patterns):
61
96
  continue
62
97
  if not config.include_tests and _is_test_file(relative_path):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: redup
3
- Version: 0.4.26
3
+ Version: 0.4.28
4
4
  Summary: Code duplication analyzer and refactoring planner for LLMs
5
5
  Author-email: Tom Sapletta <tom@sapletta.com>
6
6
  License-Expression: Apache-2.0
@@ -107,16 +107,16 @@ Dynamic: license-file
107
107
  [![PyPI](https://img.shields.io/pypi/v/redup)](https://pypi.org/project/redup/)
108
108
  [![License: Apache-2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
109
109
  [![Python](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://python.org)
110
- [![Version](https://img.shields.io/badge/version-0.4.26-green.svg)](https://pypi.org/project/redup/)
110
+ [![Version](https://img.shields.io/badge/version-0.4.28-green.svg)](https://pypi.org/project/redup/)
111
111
 
112
112
 
113
113
  ## AI Cost Tracking
114
114
 
115
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.4.26-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
116
- ![AI Cost](https://img.shields.io/badge/AI%20Cost-$33.53-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-23.4h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
115
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.4.28-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
116
+ ![AI Cost](https://img.shields.io/badge/AI%20Cost-$33.81-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-24.6h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
117
117
 
118
- - 🤖 **LLM usage:** $33.5345 (70 commits)
119
- - 👤 **Human dev:** ~$2343 (23.4h @ $100/h, 30min dedup)
118
+ - 🤖 **LLM usage:** $33.8130 (71 commits)
119
+ - 👤 **Human dev:** ~$2459 (24.6h @ $100/h, 30min dedup)
120
120
 
121
121
  Generated on 2026-05-19 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
122
122
 
@@ -3,6 +3,7 @@
3
3
  import tempfile
4
4
  from pathlib import Path
5
5
 
6
+ from redup.core import scanner_filters
6
7
  from redup.core.models import ScanConfig
7
8
  from redup.core.scanner import (
8
9
  _extract_function_blocks_python,
@@ -87,3 +88,22 @@ def test_scan_project_target_files_only():
87
88
  assert stats.files_skipped == 0
88
89
  paths = {Path(f.path).name for f in files}
89
90
  assert paths == {"b.py"}
91
+
92
+
93
+ def test_scan_project_target_files_does_not_walk_tree(monkeypatch):
94
+ with tempfile.TemporaryDirectory() as tmpdir:
95
+ root = Path(tmpdir)
96
+
97
+ (root / "a.py").write_text("def foo():\n return 1\n")
98
+ (root / "b.py").write_text("def bar():\n return 2\n")
99
+
100
+ def fail_walk(*args, **kwargs):
101
+ raise AssertionError("target-file scans should not walk the project tree")
102
+
103
+ monkeypatch.setattr(scanner_filters.os, "walk", fail_walk)
104
+
105
+ config = ScanConfig(root=root, target_files=["b.py"])
106
+ files, stats = scan_project(config)
107
+
108
+ assert stats.files_scanned == 1
109
+ assert {Path(f.path).name for f in files} == {"b.py"}
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes