Skip to main content

biston

A structural clone detector for Python code. Written in Rust.

It parses Python files with tree-sitter, normalizes the AST, and finds functions that are structurally similar to each other.

Install

uv add biston

Or build from source:

cargo build --release

Usage

biston <COMMAND>

Commands

biston scan

Scan a directory for code clones.

Usage: biston scan [OPTIONS] [PATH]

Arguments:
  [PATH]  Directory to scan [default: .]

Options:
      --format <FORMAT>        Output format [possible values: text, json, sarif]
      --min-lines <MIN_LINES>  Minimum function length in executable lines (alias: sets both tier floors)
      --exact-min-lines <N>    Executable lines the shorter function needs for an exact match [default: 5]
      --similar-min-lines <N>  Executable lines the shorter function needs for a fuzzy match [default: 9]
      --exact-min-stmts <N>    Statements a body needs for an exact match [default: 3]
      --exact-min-fragment-lines <N>    Executable lines an exactly-matched contained run needs [default: 10]
      --similar-min-fragment-lines <N>  Executable lines a fuzzily-matched contained run needs [default: 15]
      --threshold <THRESHOLD>  Similarity threshold (0.0 - 1.0)
      --config <CONFIG>        Config file directory (looks for biston.toml or pyproject.toml)
      --tests-only             Restrict the scan to Python test files (overrides include/exclude)
      --containment            Also report functions already implemented by a run of another
      --suggest                Generate abstraction suggestions for similar pairs
      --files <FILE>           Only emit pairs involving this file (repeat for multiple)
      --files-from <PATH>      Read focus file list from PATH, or `-` for stdin
  -h, --help                   Print help

biston stats

Show statistics about scan findings.

Usage: biston stats [OPTIONS] [PATH]

Arguments:
  [PATH]  Directory to scan [default: .]

Options:
      --format <FORMAT>        Output format [possible values: text, json, sarif]
      --min-lines <MIN_LINES>  Minimum function length in executable lines (alias: sets both tier floors)
      --exact-min-lines <N>    Executable lines the shorter function needs for an exact match [default: 5]
      --similar-min-lines <N>  Executable lines the shorter function needs for a fuzzy match [default: 9]
      --exact-min-stmts <N>    Statements a body needs for an exact match [default: 3]
      --exact-min-fragment-lines <N>    Executable lines an exactly-matched contained run needs [default: 10]
      --similar-min-fragment-lines <N>  Executable lines a fuzzily-matched contained run needs [default: 15]
      --threshold <THRESHOLD>  Similarity threshold (0.0 - 1.0)
      --config <CONFIG>        Config file directory (looks for biston.toml or pyproject.toml)
      --tests-only             Restrict the scan to Python test files (overrides include/exclude)
      --containment            Also report functions already implemented by a run of another
      --files <FILE>           Only emit pairs involving this file (repeat for multiple)
      --files-from <PATH>      Read focus file list from PATH, or `-` for stdin
  -h, --help                   Print help
Scanning tests only

Test suites often accumulate duplication (near-identical cases that could be @pytest.mark.parametrize, copy-pasted arrange/act/assert blocks). By default biston excludes test files so production-code findings stay focused. Pass --tests-only to flip the scope and scan only test files:

biston scan --tests-only
biston stats --tests-only

The flag replaces include with common Python test patterns (**/test_*.py, **/*_test.py, **/conftest.py, tests/**/*.py) and clears exclude. Other knobs (the size floors, threshold, normalization) are left untouched — tune them separately in biston.toml if you want different defaults for a test run.

Commit-hook use (focus files)

--files / --files-from let you restrict reporting to pairs involving a specific set of files, while still scanning the whole repo so cross-file clones between those files and the rest of the tree are detected.

For a pre-commit hook, pipe git diff --name-only through --files-from -:

git diff --name-only --diff-filter=ACM -- '*.py' \
  | biston scan --files-from - .

An empty list (no Python files changed) correctly emits no pairs. Prefer --files-from over --files $(git diff --name-only) — the latter expands to an empty flag when nothing changed, which reverts to a full-repo scan.

Configuration

Settings can go in biston.toml or under [tool.biston] in pyproject.toml. If both files exist, biston.toml takes priority. CLI flags override config file settings.

[scan]

Setting Default Description
exact_min_lines 5 Executable lines the shorter function needs for an exact match to be reported
similar_min_lines 9 Executable lines the shorter function needs for a fuzzy match to be reported
exact_min_stmts 3 Statements a body needs for an exact match to be reported (exact tier only)
threshold 0.85 Similarity threshold for the fuzzy tier (0.0–1.0)
min_lines Retained alias: sets both line floors at once
exclude ["tests/**", "**/conftest.py", "migrations/**"] File patterns to exclude
include ["**/*.py"] File patterns to include

Floors are counted in executable lines — source lines holding at least one token that survives normalization. Comments, docstrings and blank lines never count. See How acceptance works for the two tier tables and the reasoning behind them.

[normalization]

Setting Default Description
anonymize_locals true Replace local variable names
anonymize_literals false Replace literal values
strip_decorators true Remove decorators from AST
strip_type_annotations true Remove type hints
sort_commutative false Sort commutative operations

[output]

Setting Default Description
format "text" Output format (text, json, or sarif)
group_overlapping true Group overlapping clones
max_results 50 Maximum number of results
show_source true Display source code in output
context_lines 3 Number of context lines around clones

[suggest]

Setting Default Description
enabled false Enable suggestion generation
min_quality 0.6 Minimum template coverage score (0.0–1.0)
max_holes 5 Maximum holes before suppressing
render_python true Render templates as Python source

[containment]

Directed detection: one function already implements the leading or trailing run of another's body. Off by default; see Containment.

Setting Default Description
enabled false Enable containment detection (or pass --containment)
exact_min_fragment_lines 10 Executable lines an exactly matched run needs
similar_min_fragment_lines 15 Executable lines a fuzzily matched run needs
min_fragment_lines Retained alias: sets both fragment floors at once
min_ratio 0.30 Contained function size / container size
threshold 0.85 Minimum containment coefficient (0.0–1.0)
size_balance 1.25 Largest tolerated size ratio between the function and the run
max_run_fraction 0.85 Largest share of the container's statements a run may span
max_probes_per_function 12 Cap on candidate-generating probes per function

[suppress]

Setting Default Description
files [] File glob patterns to suppress entirely

Example biston.toml

[scan]
exact_min_lines = 6
similar_min_lines = 15
threshold = 0.8
exclude = ["vendor/"]
include = ["src/**/*.py"]

[normalization]
anonymize_locals = false
anonymize_literals = true

[output]
format = "json"
max_results = 100

[suggest]
enabled = true
min_quality = 0.8

Inline suppression

You can also suppress findings with Python comments:

  • # biston: ignore-file — suppress the entire file (must appear in the first 5 lines)
  • # biston: ignore — suppress a single function (place in the function body or on the preceding line)

When scan or overview reports clones, the text output ends with a one-line reminder of these options. Run biston usage for the full reference at any time:

biston usage

Documentation

Full docs at https://mojzis.github.io/biston/.

License

MIT

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

biston-0.6.0.tar.gz (952.1 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

biston-0.6.0-py3-none-win_amd64.whl (1.6 MB view details)

Uploaded Python 3Windows x86-64

biston-0.6.0-py3-none-manylinux_2_28_x86_64.whl (1.7 MB view details)

Uploaded Python 3manylinux: glibc 2.28+ x86-64

biston-0.6.0-py3-none-macosx_11_0_arm64.whl (1.5 MB view details)

Uploaded Python 3macOS 11.0+ ARM64

File details

Details for the file biston-0.6.0.tar.gz.

File metadata

  • Download URL: biston-0.6.0.tar.gz
  • Upload date:
  • Size: 952.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for biston-0.6.0.tar.gz
Algorithm Hash digest
SHA256 2e5da919216e3be1fd5e2c74af65ea67742d242c733c6f1f13807548f3678da8
MD5 596c871ce74b5d282b7e2b5e5e31bec5
BLAKE2b-256 9692866cb79ac80b8bdece010d5bccac1e65643a1d11ac40ea9b0b256e56313e

See more details on using hashes here.

Provenance

The following attestation bundles were made for biston-0.6.0.tar.gz:

Publisher: release.yml on mojzis/biston

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file biston-0.6.0-py3-none-win_amd64.whl.

File metadata

  • Download URL: biston-0.6.0-py3-none-win_amd64.whl
  • Upload date:
  • Size: 1.6 MB
  • Tags: Python 3, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for biston-0.6.0-py3-none-win_amd64.whl
Algorithm Hash digest
SHA256 1f741c0616a7925eb9feade73d21777f4f8ab8b8b7702b8a981d7f45fbe3047b
MD5 319539bad5475ef9ab6e62b7be6cb660
BLAKE2b-256 1a41f7be1b3ea23a424a557c356b67798a00d4cd0df96f631f9d14ce61350488

See more details on using hashes here.

Provenance

The following attestation bundles were made for biston-0.6.0-py3-none-win_amd64.whl:

Publisher: release.yml on mojzis/biston

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file biston-0.6.0-py3-none-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for biston-0.6.0-py3-none-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 1789aaee6ae9978ca2ef5c966ba895a8626f089e1421a116d2607b1224b06a3d
MD5 19b47cfaff263c31414cc4832ebb94a2
BLAKE2b-256 f8c3037a6e9cd9eef78c497cf8d0278bc73fe7cbecfef4db9808643deb261a11

See more details on using hashes here.

Provenance

The following attestation bundles were made for biston-0.6.0-py3-none-manylinux_2_28_x86_64.whl:

Publisher: release.yml on mojzis/biston

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file biston-0.6.0-py3-none-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for biston-0.6.0-py3-none-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 c0efac0ac6237bf647bcbc2e78ba836e96171b7d7fb565665ef3d5dab889a6c4
MD5 4d0c5327b2c84f60c8d061deae4ae245
BLAKE2b-256 e91565e51dd32c61a3a07c3a6d9b3f4bd40141f231fee87929c84db9aa5e6909

See more details on using hashes here.

Provenance

The following attestation bundles were made for biston-0.6.0-py3-none-macosx_11_0_arm64.whl:

Publisher: release.yml on mojzis/biston

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.6.0 This release

4 files

0.5.5

4 files

0.5.4

4 files

0.5.3

4 files

0.5.2

4 files

0.5.1

4 files

0.5.0

4 files

0.4.0

4 files

0.3.0

4 files

0.2.0

4 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page