CloneHunter finds duplicate code across mixed-language repositories. It uses function-aware analysis for Python and windows-based analysis for other code files, with evidence so you can decide what to refactor.
Under the hood, CloneHunter combines snippet generation (function/window/call-expansion), transformer-based code embeddings (CodeBERT via candle), vector similarity search, and lexical scoring before rolling matches up into findings and HTML/JSON/SARIF reports. This is intentionally not a lightweight grep-style checker: it runs a semantic retrieval pipeline with model inference and indexing to catch harder, non-trivial duplicate patterns.
Download the pre-built binary for your platform from GitHub Releases:
| Platform | File |
|---|---|
| macOS (Apple Silicon) | clonehunter-aarch64-apple-darwin.tar.gz |
| Linux x86_64 | clonehunter-x86_64-unknown-linux-musl.tar.gz |
# macOS / Linux example
curl -L https://github.com/drogers0/clonehunter/releases/latest/download/clonehunter-aarch64-apple-darwin.tar.gz | tar xz
sudo mv clonehunter /usr/local/bin/cargo install --git https://github.com/drogers0/clonehuntergit clone https://github.com/drogers0/clonehunter
cd clonehunter
cargo build --release
# Binary is at target/release/clonehunterFirst run: CodeBERT model weights (~440 MB) are downloaded from Hugging Face on first use and cached in
~/.cache/huggingface/hub/. Subsequent runs use the local cache. Use--embedder stubfor instant runs without any model download.
| Platform | Recommended backend | Build flag | Speed (click benchmark) |
|---|---|---|---|
| macOS arm64 + Metal GPU | MLX (opt-in sidecar) | --features mlx |
~47s |
| macOS arm64, no sidecar | candle-CPU + Accelerate | --features accelerate |
~750s |
| Linux x86_64 / arm64, CPU | ORT CPU (--embedder onnx) |
--features onnx |
~357s |
| Linux x86_64 / arm64, NVIDIA GPU | candle-CUDA | --features cuda |
~14s |
The default build (no features) uses candle-CPU and works everywhere.
The MLX backend uses Apple's Metal GPU for embedding inference. It is the fastest backend (~47s vs ~50s PyTorch-MPS on the click benchmark) with exact detection parity.
Requirements: Apple Silicon Mac, Python 3.11+ (for setup only, not at runtime).
# One-time setup: download and install prebuilt MLX library
./scripts/setup-mlx.sh
# Build with MLX support
CLONEHUNTER_MLX_PREBUILT=~/.local/share/clonehunter/mlx cargo build --release --features mlx
# Fix runtime library path
install_name_tool -add_rpath ~/.local/share/clonehunter/mlx/lib target/release/clonehunter
# Run with MLX
./target/release/clonehunter scan . --embedder mlxNote: The MLX build is not a single binary — it requires
libmlx.dylib(~16 MB) andmlx.metallib(~85 MB) at runtime. If you seeLibrary not loaded: @rpath/libmlx.dylib, run theinstall_name_toolcommand above or setDYLD_LIBRARY_PATH=~/.local/share/clonehunter/mlx/lib.
The ORT backend is the fast CPU-only alternative — ~3× faster than candle-CPU and ships
as a single statically-linked binary (no runtime dylib). It requires a pre-exported
model.onnx.
# Build
cargo build --release --features onnx
# Export model (requires PyTorch + transformers):
# python -c "
# from transformers import AutoModel
# import torch
# model = AutoModel.from_pretrained('microsoft/codebert-base')
# dummy = torch.zeros(1, 8, dtype=torch.long)
# torch.onnx.export(model, (dummy, dummy, dummy),
# '~/.cache/clonehunter/onnx/codebert-base/model.onnx',
# input_names=['input_ids','attention_mask','token_type_ids'],
# output_names=['last_hidden_state'], opset_version=18,
# dynamic_axes={n: {0:'batch',1:'seq'} for n in
# ['input_ids','attention_mask','token_type_ids','last_hidden_state']})"
# Run
./target/release/clonehunter scan . --embedder onnx# Build (requires CUDA toolkit)
cargo build --release --features cuda
# Run
./target/release/clonehunter scan . --embedder codebert --device cudaclonehunter scan . --format html --out clonehunter_report.htmlScan a repository (defaults to HTML and clonehunter_report.html; output extension follows --format):
clonehunter scan .Generate a JSON report:
clonehunter scan . --format json --out report.jsonGenerate an HTML report:
clonehunter scan . --format html --out report.htmlGenerate a SARIF report (for integration with GitHub Code Scanning):
clonehunter scan . --format sarif --out report.sarifDiff scan (scan only changed files relative to a git ref):
clonehunter diff --base HEAD --format json --out diff.json- Python files: parsed with tree-sitter AST extraction and analyzed with function/window snippets.
- Other code files: analyzed in windows-only mode by file content.
- Cross-file-type comparisons are supported.
- Results can vary by language and repository shape; tune thresholds/windows for best quality.
Scores are composite: embedding similarity blended with lexical similarity.
composite = (1 - lexical_weight) * embedding + lexical_weight * lexical
Matches are filtered by lexical_min_ratio, and then the composite score is compared against the relevant threshold (func, win, or exp).
Place a clonehunter.toml file in your repository root to configure CloneHunter:
engine = "semantic"
cluster_findings = false
cluster_min_size = 2
[thresholds]
func = 0.92
win = 0.90
exp = 0.90
min_window_hits = 1
lexical_min_ratio = 0.5
lexical_weight = 0.3
[windows]
window_lines = 12
stride_lines = 6
min_nonempty = 4
[expansion]
enabled = false
depth = 1
max_chars = 4000
[index]
name = "brute"
top_k = 25
[cache]
path = "~/.cache/clonehunter"
[embedder]
name = "codebert"
model = "microsoft/codebert-base"
revision = "main"
max_length = 256
batch_size = 16
device = "auto"Migrating from pyproject.toml: Move your
[tool.clonehunter]settings into a standaloneclonehunter.tomlfile in your repository root. The key names are unchanged; drop the[tool.clonehunter]prefix (e.g.,[tool.clonehunter.thresholds]→[thresholds]).
By default, CLI scans apply the monorepo repotype preset unless overridden with --repotype or --repotype none.
clonehunter scan [PATHS...] [--format json|html|sarif] [--out FILE]
--engine semantic|sonarqube
--embedder codebert|stub|onnx|mlx
--index brute|faiss
--threshold-func FLOAT
--threshold-win FLOAT
--threshold-exp FLOAT
--min-window-hits INT
--lexical-min-ratio FLOAT
--lexical-weight FLOAT
--window-lines INT
--stride-lines INT
--min-nonempty INT
--expand-calls
--expand-depth INT
--expand-max-chars INT
--cache-path PATH
--cluster
--cluster-min-size INT
--repotype dotnet|go|java|kotlin|monorepo|node|none|php|python|react|ruby|rust|swift|cpp
# repeatable preset globs
--include-globs GLOB # repeatable; merged with config includes
--exclude-globs GLOB # repeatable; merged with config excludes
--device auto|cpu|cuda
clonehunter diff --base REF [--format json|html|sarif] [--out FILE]
--repotype is additive and can be mixed (for example --repotype python --repotype react).
If --repotype is omitted, CloneHunter defaults to monorepo (scans all supported languages).
Use --repotype none to disable all repotype presets (scans 0 files unless --include-globs is also passed).
Merge order: clonehunter.toml globs → --repotype presets → explicit --include-globs/--exclude-globs.
When the same glob appears in both include/exclude sets, the most recent CLI layer wins.
Example mixed-language scan with custom overrides:
clonehunter scan . \
--repotype python \
--repotype react \
--repotype cpp \
--exclude-globs "**/__generated__/**" \
--include-globs "**/*.txt" \
--format html --out report.htmlExample reports live in the examples/ folder:
examples/clonehunter_report.htmlexamples/clonehunter_report.jsonexamples/clonehunter_report.sarifexamples/clonehunter_diff.jsonexamples/clonehunter_diff_report.html
Generate the example reports:
clonehunter scan . --format html --out examples/clonehunter_report.html
clonehunter scan . --format json --out examples/clonehunter_report.json
clonehunter scan . --format sarif --out examples/clonehunter_report.sarif
clonehunter diff --base HEAD --format json --out examples/clonehunter_diff.json
clonehunter diff --base HEAD --format html --out examples/clonehunter_diff_report.html- If you see too many false positives, increase
lexical_min_ratioand/orlexical_weight. - Increase
min_window_hitsto require stronger evidence. - Exclude tests or generated code via
exclude_globs.
- Semantic similarity is approximate, not guaranteed equivalence.
- Python findings are generally richer due to AST/function context.
- Non-Python findings use windows-only analysis and may require threshold/window tuning.
- Very small functions are harder to compare meaningfully.
- FAISS index is not implemented; the
--index faissflag is accepted for CLI compatibility but always uses the brute-force index. - Embedding arithmetic uses f32 (candle default); near-equal cosine scores may differ slightly from the Python baseline.
- The
mlxembedder backend is Apple Silicon only and requires a sidecar library (~101 MB). See the Apple MLX backend section for build instructions.
# Run the full test suite
cargo test
# Format and lint
cargo fmt --check
cargo clippy --all-targets -- -D warnings
# Fast dev loop without model download (stub embedder is deterministic)
CLONEHUNTER_EMBEDDER=stub cargo run -- scan .
# Full scan with CodeBERT (downloads ~440 MB on first run)
cargo run --release -- scan . --format html