Poniżej masz szczegółowy plan refaktoryzacji całego projektu reDUP, nie tylko funkcji intent. Traktuję to jako plan techniczny do wykonania etapami: najpierw porządkowanie architektury, potem stabilizacja pipeline, potem wdrożenie Intent DSL / RIDL, potem CLI/MCP/reportery/testy.
Założenie główne: nie przebudowywać wszystkiego naraz. reDUP ma już naturalne miejsca rozszerzeń: skaner, modele, pipeline wykrywania, CLI, reportery i testy. Dlatego nową funkcję intent duplication najlepiej dodać jako osobny tor analizy obok exact/structural/near/fuzzy/semantic, bez mieszania jej z obecnym hasherem i matcherem.
Docelowo reDUP powinien mieć czystą architekturę:
CLI / MCP
↓
Config / ScanConfig
↓
Scanner / Extractors
↓
CodeBlock[]
↓
Detection Pipeline
├── exact duplicates
├── structural duplicates
├── near duplicates
├── fuzzy duplicates
├── intent duplicates
└── semantic duplicates
↓
DuplicateGroup[]
↓
Planner / Decision / Refactor Suggestions
↓
Reporters JSON / YAML / Markdown / TOON / MCP
Główne cele:
1. Uporządkować modele domenowe.
2. Odchudzić duże moduły i hotspoty.
3. Rozdzielić skanowanie, detekcję, grupowanie, planowanie i raportowanie.
4. Dodać Intent DSL jako osobny, algorytmiczny detektor.
5. Ujednolicić CLI, MCP i config.
6. Wzmocnić testy kontraktowe i e2e.
7. Przygotować projekt do dalszego rozwoju bez dokładania chaosu.
Na podstawie mapy projektu i wcześniejszego planu największe ryzyka są takie:
1. Za dużo odpowiedzialności w CLI.
2. Pipeline jest funkcjonalny, ale zaczyna puchnąć.
3. Modele wynikowe mogą się rozrastać pod kolejne detektory.
4. Reportery mają dużo logiki formatowania i przeliczania.
5. MCP częściowo powiela logikę CLI/config.
6. Skaner ma dużo wariantów: normal, ultra_fast, memory_optimized, parallel.
7. Intent duplication może pogłębić chaos, jeśli zostanie wklejony bez warstwy domenowej.
Najważniejsze: nie dodawać kolejnych if intent_enabled w losowych miejscach. Dla intentów trzeba stworzyć osobny pakiet i wpiąć go tylko przez pipeline.
src/redup/core/
hasher.py
matcher.py
lsh_matcher.py
universal_fuzzy.py
semantic.py
scanner/
pipeline/
planner.py
decision.py
src/redup/core/
models.py
config.py
scanner/
__init__.py
filters.py
loader.py
extraction.py
strategies.py
detection/
__init__.py
exact.py
structural.py
near.py
fuzzy.py
semantic.py
intent.py
intent/
__init__.py
schema.py
ridl.py
comments.py
parser.py
normalizer.py
signature.py
scoring.py
detector.py
grouping.py
pipeline/
__init__.py
phases.py
duplicate_finder.py
groups.py
result_builder.py
planning/
decision.py
planner.py
refactor_advisor.py
reporting/
evidence.py
metrics.py
Nie musisz robić tego od razu. To jest kierunek docelowy.
Zanim ruszysz refaktoryzację, trzeba zabezpieczyć obecny stan.
Uruchomić:
pytest -q
ruff check .
python -m redup scan ./src --format json
python -m redup scan ./src --format toon
python -m redup check ./srcZapisać wyniki bazowe:
docs/refactor/baseline/
pytest.txt
ruff.txt
scan-src.json
scan-src.toon
check-src.txt
Masz punkt odniesienia. Każdy kolejny etap porównujesz do baseline.
src/redup/core/models.py
models.py jest centralny dla całego systemu, więc każda nowa funkcja może go rozpychać. Jeśli dodasz pola typu intent_label, intent_signature, intent_score_breakdown bezpośrednio do DuplicateGroup, za chwilę model będzie zawierał pola dla każdego detektora.
Dodać ogólny model dowodów:
class DuplicateEvidence(BaseModel):
kind: str
score: float
reason: dict[str, Any] = Field(default_factory=dict)Rozszerzyć DuplicateGroup:
evidence: list[DuplicateEvidence] = Field(default_factory=list)
metadata: dict[str, Any] = Field(default_factory=dict)Dodać DuplicateType.INTENT:
class DuplicateType(str, Enum):
EXACT = "exact"
STRUCTURAL = "structural"
NEAR = "near"
FUZZY = "fuzzy"
SEMANTIC = "semantic"
INTENT = "intent"tests/test_models.py
Dodać:
def test_duplicate_type_has_intent():
assert DuplicateType.INTENT.value == "intent"
def test_duplicate_group_accepts_evidence():
...Każdy detektor może podać własne wyjaśnienie bez psucia modeli.
src/redup/core/models.py
src/redup/core/config.py
src/redup/cli_app/config_builder.py
src/redup/mcp/handlers.py
Konfiguracja jest rozproszona między CLI, ScanConfig, config builderem i MCP.
ScanConfig powinien być jednym źródłem prawdy.
Dodać sekcję intent:
intent_enabled: bool = False
intent_threshold: float = 0.84
intent_source: Literal["explicit", "comment", "heuristic"] = "explicit"
intent_format: Literal["ridl", "yaml", "auto"] = "auto"
intent_context_lines: int = 12Nie używałbym jednocześnie:
intent_comments_only
intent_require_tag
intent_mode
bo to będzie mylące. Lepiej mieć jedno:
intent_source = explicit | comment | heuristic
[redup.intent]
enabled = true
threshold = 0.84
source = "explicit"
format = "auto"
context_lines = 12tests/test_config.py
tests/test_e2e.py
tests/test_mcp_server.py
CLI, config file i MCP tworzą ten sam ScanConfig.
src/redup/cli_app/main.py
src/redup/cli_app/scan_commands.py
src/redup/cli_app/compare_command.py
src/redup/cli_app/tasks_command.py
src/redup/cli_app/output_writer.py
CLI powinno tylko:
1. zebrać parametry,
2. zbudować config,
3. odpalić core,
4. przekazać wynik do reportera.
Nie powinno zawierać logiki detekcji, filtrowania, grupowania ani refaktoryzacji.
Wydzielić:
src/redup/cli_app/options.py
src/redup/cli_app/commands/scan.py
src/redup/cli_app/commands/compare.py
src/redup/cli_app/commands/check.py
src/redup/cli_app/commands/config.py
src/redup/cli_app/commands/tasks.py
Jeżeli nie chcesz przebudowywać katalogów od razu, najpierw wydziel tylko helpery:
src/redup/cli_app/scan_options.py
src/redup/cli_app/scan_runner.py
def scan_command(...):
config = build_scan_config_from_cli(...)
result = run_scan(config)
write_results(result, format=format, output=output)scan_commands.py nie powinien mieć logiki skanowania ani detekcji.
src/redup/core/pipeline/__init__.py
src/redup/core/pipeline/duplicate_finder.py
src/redup/core/pipeline/phases.py
src/redup/core/pipeline/groups.py
Pipeline ma kilka wariantów:
analyze
analyze_optimized
analyze_parallel
find_duplicates_phase_optimized
find_duplicates_phase_lazy
To działa, ale nowy detektor intent zwiększy liczbę gałęzi.
Wprowadzić jawne fazy:
ScanPhase
ProcessBlocksPhase
DetectionPhase
GroupFinalizePhase
SuggestionPhase
ReportPhase
Technicznie:
class DetectionEngine(Protocol):
name: str
def enabled(self, config: ScanConfig) -> bool:
...
def detect(self, blocks: list[CodeBlock], config: ScanConfig) -> list[DuplicateGroup]:
...DETECTORS = [
ExactDuplicateDetector(),
StructuralDuplicateDetector(),
NearDuplicateDetector(),
FuzzyDuplicateDetector(),
IntentDuplicateDetector(),
SemanticDuplicateDetector(),
]Pipeline:
groups = []
for detector in DETECTORS:
if detector.enabled(config):
groups.extend(detector.detect(blocks, config))Nie dodajesz kolejnych if-ów w duplicate_finder.py.
Jeśli to za duża zmiana na raz, zostaw obecny pipeline, ale dodaj jedną funkcję:
def find_intent_groups(blocks, config) -> list[DuplicateGroup]:
...i wpiąć ją po find_near_duplicate_groups, przed find_semantic_groups.
src/redup/core/detection/
__init__.py
exact.py
structural.py
near.py
fuzzy.py
semantic.py
intent.py
hasher.py → detection/exact.py + detection/structural.py
lsh_matcher.py → detection/near.py
universal_fuzzy.py → detection/fuzzy.py
semantic.py → detection/semantic.py
intent/detector.py → detection/intent.py
Na początku nie przenoś całego kodu. Możesz zrobić adaptery:
class ExactDuplicateDetector:
def detect(...):
return find_exact_groups(...)Pipeline zna detektory, a nie szczegóły hashy/LSH/fuzzy.
src/redup/core/scanner/__init__.py
src/redup/core/scanner_filters.py
src/redup/core/scanner_loader.py
src/redup/core/scanner_utils.py
src/redup/core/scanner_types.py
src/redup/core/ts_extractor/
Skaner ma kilka trybów i dużo odpowiedzialności:
- zbieranie plików,
- filtrowanie,
- czytanie,
- cache,
- ekstrakcja bloków,
- równoległość,
- fallbacki językowe.
scanner/
filters.py # include/exclude/test files/target files
loader.py # read file, mmap, memory cache
extractor.py # source -> CodeBlock[]
strategies.py # simple, parallel, memory_optimized
service.py # scan_project()
scan_project() powinno tylko orkiestrwać:
files = collect_files(config)
sources = load_sources(files, strategy)
blocks = extract_blocks(sources, config)
return ScannedProject(files, blocks, stats)Ekstrakcja komentarzy intent nie powinna być częścią skanera. Skaner daje CodeBlock, a intent parser działa później.
src/redup/core/scanner_types.py
Do intentów potrzebujesz stabilnego identyfikatora i metadanych.
class CodeBlock(BaseModel):
file_path: str
start_line: int
end_line: int
content: str
language: str | None = None
block_type: Literal["line", "block", "function", "method", "class", "file"] = "block"
name: str | None = None
class_name: str | None = None
@property
def stable_id(self) -> str:
...Każdy detektor dostaje ten sam format wejściowy.
src/redup/core/intent/
__init__.py
schema.py
ridl.py
comments.py
parser.py
normalizer.py
signature.py
scoring.py
detector.py
grouping.py
Wcześniejszy plan też wskazywał osobny pakiet core/intent z odpowiedzialnościami: ekstrakcja komentarzy, parsowanie intencji, normalizacja, podpis intencji i detekcja podobieństw.
# @intent parse:extensions !p2 @cli in:raw out:list fx:none #config scope:functiondef parse_intent_tag_line(line: str) -> IntentDescriptor | None:
...def build_intent_signature(record: IntentRecord) -> IntentSignature:
...def score_intent_similarity(a: IntentSignature, b: IntentSignature) -> tuple[float, dict]:
...class IntentDuplicateDetector:
def detect(self, blocks: list[CodeBlock], config: ScanConfig) -> list[DuplicateGroup]:
...Domyślnie tylko:
intent_source = "explicit"
czyli analizujemy tylko @intent, nie każdy komentarz.
src/redup/core/intent/grouping.py
src/redup/core/pipeline/groups.py
Intent duplicate nie jest tym samym co exact/structural duplicate.
Nie scalać automatycznie:
INTENT + EXACT
INTENT + STRUCTURAL
INTENT + NEAR
INTENT + SEMANTIC
def group_intent_duplicate_pairs(pairs: list[IntentDuplicatePair]) -> list[set[str]]:
...DuplicateGroup(
duplicate_type=DuplicateType.INTENT,
similarity=avg_score,
fragments=...,
evidence=[
DuplicateEvidence(
kind="intent",
score=avg_score,
reason={...},
)
],
)src/redup/reporters/json_reporter.py
src/redup/reporters/yaml_reporter.py
src/redup/reporters/markdown_reporter.py
src/redup/reporters/toon_reporter.py
src/redup/reporters/enhanced_reporter.py
src/redup/reporters/code2llm_reporter.py
Reportery mogą zawierać zbyt dużo logiki domenowej.
Wydzielić warstwę serializacji:
src/redup/reporters/serializers.py
src/redup/reporters/common.py
src/redup/reporters/evidence.py
Reporter nie powinien obliczać logiki intentów. Ma tylko renderować:
group.evidence
group.metadata
group.fragmentsNajpierw rozszerzyć JSON, potem Markdown/TOON.
{
"type": "intent",
"similarity": 0.91,
"evidence": [
{
"kind": "intent",
"score": 0.91,
"reason": {
"action_score": 1.0,
"object_score": 0.85,
"domain_score": 1.0
}
}
]
}src/redup/mcp/handlers.py
src/redup/mcp/schemas.py
src/redup/mcp/server.py
src/redup/mcp/utils.py
MCP nie powinien osobno interpretować reguł analizy. Powinien korzystać z tego samego ScanConfig i tego samego pipeline co CLI.
W _build_scan_config dodać:
intent_enabled=params.get("intent", False)
intent_threshold=params.get("intent_threshold", 0.84)
intent_source=params.get("intent_source", "explicit")Dodać parametry:
{
"intent": {
"type": "boolean"
},
"intent_threshold": {
"type": "number",
"default": 0.84
},
"intent_source": {
"type": "string",
"enum": ["explicit", "comment", "heuristic"]
}
}def test_analyze_project_accepts_intent_options():
...src/redup/core/refactor_advisor.py
To jest warstwa LLM. Nie powinna być wymieszana z deterministycznymi regułami.
src/redup/core/planning/
planner.py # deterministic suggestions
decision.py # deterministic recommendations
refactor_advisor.py # LLM-only
prompt_builder.py
llm_response_parser.py
Intent duplication nie wymaga LLM.
LLM może później tylko pomóc:
redup intent annotate --dry-run
ale nie powinien decydować, czy coś jest duplikatem.
src/redup/core/universal_fuzzy.py
To duży moduł i może być mieszanką:
- normalizacji,
- ekstrakcji sygnatur,
- scoringu,
- detekcji.
Podzielić na:
src/redup/core/fuzzy/
signature.py
extractor.py
scoring.py
detector.py
Nie robić tego w tym samym commicie co intent. Najpierw intent, potem fuzzy cleanup.
src/redup/core/cache.py
src/redup/core/hash_cache.py
src/redup/core/scanner_cache.py
Są różne cache:
HashCache
MemoryFileCache
scanner cache
hash cache
src/redup/core/cache/
file_cache.py
hash_cache.py
scan_cache.py
intent_cache.py
Cache powinien mieć:
file_hash
comment_hash
intent_signature_hash
Cache dla intentów dopiero po działającym detektorze.
Masz testy dla:
CLI
compare
e2e
hasher
matcher
models
pipeline
planner
quality
reporters
scanner
ts_extractor
mcp
tests/test_intent_schema.py
tests/test_intent_ridl.py
tests/test_intent_comments.py
tests/test_intent_parser.py
tests/test_intent_normalizer.py
tests/test_intent_signature.py
tests/test_intent_scoring.py
tests/test_intent_detector.py
tests/test_intent_grouping.py
tests/test_intent_pipeline.py
tests/fixtures/intent/
explicit_same_intent/
explicit_different_intent/
different_code_same_intent/
similar_code_different_intent/
comments_without_intent/
class_level_intent/
file_level_intent/
project_level_intent/
1. @intent parse:extensions jest wykrywany.
2. check → validate jest normalizowane.
3. read → parse jest normalizowane.
4. różny kod + ta sama intencja daje INTENT duplicate.
5. podobny kod + różna intencja nie daje INTENT duplicate.
6. komentarze bez @intent są ignorowane w explicit mode.
7. intent group nie scala się z exact group.
8. JSON reporter pokazuje evidence.
9. CLI --intent działa.
10. MCP przyjmuje intent=true.
Próg 0.84 nie może być przypadkowy.
scripts/calibrate-intent-threshold.py
explicit: 0.82–0.84
comment: 0.88
heuristic: 0.92
Im mniej formalne źródło intencji, tym wyższy próg.
@intent DSL → wysoka wiarygodność
zwykły komentarz → średnia wiarygodność
heurystyka nazw → niższa wiarygodność
README.md
docs/architecture.md
docs/intent-dsl.md
docs/refactoring-plan.md
SUMD.md
docs/intent-dsl.md
Zawartość:
1. Czym jest RIDL.
2. Składnia @intent.
3. Priorytety !p1–!p5.
4. Scope: line/block/function/class/file/project.
5. Przykłady dla Pythona, JS, C#, SQL, HTML.
6. Jak reDUP liczy similarity.
7. Ograniczenia.
# @intent build:scan_config !p1 @scanner in:path,params out:ScanConfig fx:none #config scope:functionObecnie projekt ma wersję 0.4.29. Proponuję:
0.4.30 — refactor models/config baseline
0.4.31 — pipeline detector registry
0.4.32 — intent DSL parser
0.4.33 — intent duplicate detector
0.4.34 — CLI + JSON reporter
0.4.35 — MCP + docs
0.5.0 — stable intent duplication feature
Added
- Intent duplicate detection
- @intent DSL
- DuplicateEvidence model
Changed
- Pipeline supports detector registry
- Reporters include evidence
Fixed
- Reduced coupling between CLI and core analysis
refactor(models): add duplicate evidence metadata
Pliki:
src/redup/core/models.py
tests/test_models.py
refactor(config): centralize intent scan options
Pliki:
src/redup/core/models.py
src/redup/core/config.py
src/redup/cli_app/config_builder.py
refactor(pipeline): isolate duplicate detection phases
Pliki:
src/redup/core/pipeline/duplicate_finder.py
src/redup/core/pipeline/groups.py
tests/test_pipeline.py
feat(intent): add RIDL schema and parser
Pliki:
src/redup/core/intent/__init__.py
src/redup/core/intent/schema.py
src/redup/core/intent/ridl.py
tests/test_intent_ridl.py
feat(intent): add intent normalization and signatures
Pliki:
src/redup/core/intent/normalizer.py
src/redup/core/intent/signature.py
tests/test_intent_normalizer.py
tests/test_intent_signature.py
feat(intent): add scoring and duplicate detector
Pliki:
src/redup/core/intent/scoring.py
src/redup/core/intent/detector.py
src/redup/core/intent/grouping.py
tests/test_intent_scoring.py
tests/test_intent_detector.py
feat(pipeline): include intent duplicate phase
Pliki:
src/redup/core/pipeline/duplicate_finder.py
tests/test_intent_pipeline.py
feat(cli): expose intent detection options
Pliki:
src/redup/cli_app/main.py
src/redup/cli_app/scan_commands.py
src/redup/cli_app/config_builder.py
tests/test_e2e.py
feat(reporters): render intent evidence
Pliki:
src/redup/reporters/json_reporter.py
src/redup/reporters/markdown_reporter.py
src/redup/reporters/toon_reporter.py
tests/test_reporters.py
feat(mcp): support intent analysis parameters
Pliki:
src/redup/mcp/handlers.py
src/redup/mcp/schemas.py
tests/test_mcp_server.py
docs(intent): document RIDL and intent duplication
Pliki:
README.md
docs/intent-dsl.md
SUMD.md
1. DuplicateEvidence.
2. DuplicateType.INTENT.
3. Intent DSL parser.
4. Intent signature.
5. Intent scoring.
6. Intent detector.
7. Pipeline integration.
8. JSON output.
9. CLI --intent.
10. Testy e2e.
1. Detector registry.
2. MCP integration.
3. Markdown/TOON output.
4. Config file support.
5. Documentation.
6. Threshold calibration.
1. Heuristic intent mode.
2. File-level/project-level inheritance.
3. Intent cache.
4. LLM-assisted annotation.
5. RDF/ontology export.
6. Full cleanup universal_fuzzy.
7. Full scanner split.
MVP powinien być bardzo konkretny:
Użytkownik dodaje w kodzie:
# @intent parse:extensions !p2 @cli in:raw out:list #config
Uruchamia:
python -m redup scan ./src --intent --format json
Dostaje:
DuplicateType.INTENT z evidence i similarity.
Bez MVP:
- brak LLM,
- brak heurystyki,
- brak automatycznego generowania tagów,
- brak RDF/OWL,
- brak przebudowy całego skanera.
Projekt można uznać za zrefaktoryzowany, gdy:
pytest -q
ruff check .
python -m redup scan ./src --format json
python -m redup scan ./src --intent --format json
python -m redup check ./srcprzechodzą poprawnie, a dodatkowo:
1. Pipeline obsługuje intent jako osobny detektor.
2. CLI i MCP używają tego samego ScanConfig.
3. JSON/Markdown/TOON pokazują evidence.
4. Intent duplicate nie scala się automatycznie z exact/structural.
5. Testy fixtures wykrywają różny kod o tej samej intencji.
6. Zwykłe komentarze bez @intent nie tworzą false positive w explicit mode.
7. Dokumentacja opisuje @intent DSL i przykłady.
Nie zaczynałbym od wielkiej przebudowy całego projektu. Najbezpieczniejsza ścieżka to:
1. Najpierw evidence + config.
2. Potem osobny moduł intent.
3. Potem pipeline.
4. Potem CLI/reportery.
5. Dopiero potem większe porządkowanie scanner/fuzzy/MCP.
Czyli refaktoryzacja ma iść od stabilnego jądra do interfejsów, a nie odwrotnie. Dzięki temu nie rozwalisz działającego reDUP, a jednocześnie przygotujesz projekt pod dowolnie złożone intencje: od pojedynczej linii, przez funkcję i klasę, aż po cały projekt.