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.
- {redup-0.4.26/src/redup.egg-info → redup-0.4.28}/PKG-INFO +6 -6
- {redup-0.4.26 → redup-0.4.28}/README.md +5 -5
- {redup-0.4.26 → redup-0.4.28}/pyproject.toml +1 -1
- {redup-0.4.26 → redup-0.4.28}/src/redup/__init__.py +1 -1
- {redup-0.4.26 → redup-0.4.28}/src/redup/core/scanner_filters.py +43 -8
- {redup-0.4.26 → redup-0.4.28/src/redup.egg-info}/PKG-INFO +6 -6
- {redup-0.4.26 → redup-0.4.28}/tests/test_scanner.py +20 -0
- {redup-0.4.26 → redup-0.4.28}/LICENSE +0 -0
- {redup-0.4.26 → redup-0.4.28}/setup.cfg +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/__main__.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/analysis_logic.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/cli_app/__init__.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/cli_app/compare_command.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/cli_app/config_builder.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/cli_app/fuzzy_similarity.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/cli_app/main.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/cli_app/output_writer.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/cli_app/quality_commands.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/cli_app/scan_commands.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/cli_app/scan_helpers.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/cli_app/tasks_command.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/config.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/config_handler.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/core/__init__.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/core/cache.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/core/community.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/core/comparator.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/core/config.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/core/decision.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/core/differ.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/core/fuzzy_similarity.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/core/grouper.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/core/hash_cache.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/core/hasher.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/core/lazy_grouper.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/core/lsh_matcher.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/core/matcher.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/core/models.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/core/pipeline/__init__.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/core/pipeline/duplicate_finder.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/core/pipeline/groups.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/core/pipeline/phases.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/core/pipeline_utils.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/core/planner.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/core/python_parser.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/core/refactor_advisor.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/core/scanner/__init__.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/core/scanner.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/core/scanner_cache.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/core/scanner_loader.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/core/scanner_models.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/core/scanner_types.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/core/scanner_utils.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/core/semantic.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/core/ts_extractor/__init__.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/core/ts_extractor/config.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/core/ts_extractor/dispatcher.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/core/ts_extractor/extractors/__init__.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/core/ts_extractor/extractors/base.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/core/ts_extractor/extractors/c_family.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/core/ts_extractor/extractors/dotnet.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/core/ts_extractor/extractors/markup.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/core/ts_extractor/extractors/php.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/core/ts_extractor/extractors/query.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/core/ts_extractor/extractors/ruby.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/core/ts_extractor/extractors/shell.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/core/ts_extractor/extractors/stylesheet.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/core/ts_extractor/extractors/web.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/core/ts_extractor/main.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/core/universal_fuzzy.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/core/utils/__init__.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/core/utils/diff_helpers.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/core/utils/duplicate_finders.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/core/utils/function_extractor.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/core/utils/hash_utils.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/core/utils/language_dispatcher.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/integrations/__init__.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/integrations/planfile_integration.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/mcp/__init__.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/mcp/handlers.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/mcp/schemas.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/mcp/server.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/mcp/utils.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/mcp_server.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/mcp_server_clean.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/reporters/__init__.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/reporters/code2llm_reporter.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/reporters/enhanced_reporter.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/reporters/json_reporter.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/reporters/markdown_reporter.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/reporters/toon_reporter.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/reporters/yaml_reporter.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/reporters.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup/utils.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup.egg-info/SOURCES.txt +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup.egg-info/dependency_links.txt +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup.egg-info/entry_points.txt +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup.egg-info/requires.txt +0 -0
- {redup-0.4.26 → redup-0.4.28}/src/redup.egg-info/top_level.txt +0 -0
- {redup-0.4.26 → redup-0.4.28}/tests/test_cli_import_compat.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/tests/test_compare.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/tests/test_e2e.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/tests/test_hasher.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/tests/test_matcher.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/tests/test_mcp_server.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/tests/test_models.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/tests/test_pipeline.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/tests/test_planfile_integration.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/tests/test_planner.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/tests/test_quality_commands.py +0 -0
- {redup-0.4.26 → redup-0.4.28}/tests/test_reporters.py +0 -0
- {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.
|
|
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
|
[](https://pypi.org/project/redup/)
|
|
108
108
|
[](https://opensource.org/licenses/Apache-2.0)
|
|
109
109
|
[](https://python.org)
|
|
110
|
-
[](https://pypi.org/project/redup/)
|
|
111
111
|
|
|
112
112
|
|
|
113
113
|
## AI Cost Tracking
|
|
114
114
|
|
|
115
|
-
    
|
|
116
|
+
  
|
|
117
117
|
|
|
118
|
-
- 🤖 **LLM usage:** $33.
|
|
119
|
-
- 👤 **Human dev:** ~$
|
|
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
|
[](https://pypi.org/project/redup/)
|
|
6
6
|
[](https://opensource.org/licenses/Apache-2.0)
|
|
7
7
|
[](https://python.org)
|
|
8
|
-
[](https://pypi.org/project/redup/)
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
## AI Cost Tracking
|
|
12
12
|
|
|
13
|
-
    
|
|
14
|
+
  
|
|
15
15
|
|
|
16
|
-
- 🤖 **LLM usage:** $33.
|
|
17
|
-
- 👤 **Human dev:** ~$
|
|
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
|
|
|
@@ -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.
|
|
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
|
[](https://pypi.org/project/redup/)
|
|
108
108
|
[](https://opensource.org/licenses/Apache-2.0)
|
|
109
109
|
[](https://python.org)
|
|
110
|
-
[](https://pypi.org/project/redup/)
|
|
111
111
|
|
|
112
112
|
|
|
113
113
|
## AI Cost Tracking
|
|
114
114
|
|
|
115
|
-
    
|
|
116
|
+
  
|
|
117
117
|
|
|
118
|
-
- 🤖 **LLM usage:** $33.
|
|
119
|
-
- 👤 **Human dev:** ~$
|
|
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
|
|
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
|
|
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
|
|
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
|