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.

Working with a coding agent

biston explains itself, so you do not have to. Hand your agent one line:

Run uvx biston guide at the root of this repo and follow what it says.

It prints what to do at the moment you are actually in -- how to install and wire biston up when the repo has no config, and what to do with each finding when it does. When a scan reports clones, its footer points the agent at biston guide triage, so a failed check leads back to the instructions on its own. See biston guide for the three topics and how one is chosen.

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 guide

Print short, self-contained instructions for the moment you are in. Written to be read by a coding agent -- see Working with a coding agent.

Usage: biston guide [TOPIC]

Arguments:
  [TOPIC]  setup, triage or tune. Omit to let biston choose.

With no topic, biston prints setup when it is not configured in the current directory and triage when it is; tune is a reference and is never auto-selected. Detection looks at the current directory only and never walks up, so run it at the repository root. The first line of output always says which topic you got and why:

# biston guide: not configured here -> setup
# biston guide: configured via pyproject.toml [tool.biston] -> triage

A repository counts as configured when it has a biston.toml, a [tool.biston] table in pyproject.toml, or a .pre-commit-config.yaml referencing the biston hook -- named in that order of precedence.

The three pages are also on the docs site: Setup, Triage, Tune. The CLI and the site serve the same bytes.

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.

Exit codes

Code Meaning
0 scan / stats found nothing; any other subcommand printed its output
1 scan / stats reported findings (clone pairs or containments)
2 biston could not do its job: bad usage, unreadable path, invalid config

A check aggregator can therefore run biston scan . directly, and tell a duplicated tree from a broken invocation.

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 footer pointing at biston guide triage. The footer is printed whether or not stdout is a terminal, and never in json or sarif output.

Documentation

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

License

MIT

Metadata

Release files for biston 0.7.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for biston 0.7.1
File Size Uploaded
biston-0.7.1.tar.gz 972.8 kB Details

Built distributions (wheels)

Table of built distributions (wheels) for biston 0.7.1
File Interpreter ABI Platform
biston-0.7.1-py3-none-win_amd64.whl Python 3 none Windows x86-64 Details
biston-0.7.1-py3-none-manylinux_2_28_x86_64.whl Python 3 none Linux glibc 2.28+ x86-64 Details
biston-0.7.1-py3-none-macosx_11_0_arm64.whl Python 3 none macOS 11.0+ ARM64 Details

Total release size: 5.8 MB

Release files / biston-0.7.1.tar.gz

Download URL biston-0.7.1.tar.gz
Size 972.8 kB
Tags Source
SHA-256 checksum
How to use checksums
d8706bea2c815084d96f09b49603f65dec87345ef0cc16711696a18fac1998aa
BLAKE2b-256 checksum
How to use checksums
720fb48abbcbfedd97f9c7acd098ed3b5490879ab6e4e81391217402fac36c41
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 6, 2026.

Transparency log

Release files / biston-0.7.1-py3-none-win_amd64.whl

Download URL biston-0.7.1-py3-none-win_amd64.whl
Size 1.6 MB
Tags Python 3 Windows x86-64
SHA-256 checksum
How to use checksums
d22115b7f0e8e4f0c77f373c903c8c48fd84ee631e4f94972fb5be99ed0b34c8
BLAKE2b-256 checksum
How to use checksums
d8f56934417f2d3d24ef63e15e869c864a0b4f8ea5fa4876884e7dedd6d2d125
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 6, 2026.

Transparency log

Release files / biston-0.7.1-py3-none-manylinux_2_28_x86_64.whl

Download URL biston-0.7.1-py3-none-manylinux_2_28_x86_64.whl
Size 1.8 MB
Tags Linux glibc 2.28+ x86-64 Python 3
SHA-256 checksum
How to use checksums
35064c34642a46593088145672fde9d9df33c9fd0666c3cc23b0511274cd6e95
BLAKE2b-256 checksum
How to use checksums
90e042131be764beb174c9a402aef1b5be3603679d363537b03546d5f41c751d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 6, 2026.

Transparency log

Release files / biston-0.7.1-py3-none-macosx_11_0_arm64.whl

Download URL biston-0.7.1-py3-none-macosx_11_0_arm64.whl
Size 1.5 MB
Tags Python 3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
0d41cc3b54df859acdfb8d74bf2a76e3d9a6d53b17fe93ed576190bac6ed6586
BLAKE2b-256 checksum
How to use checksums
d607fed86b072f0db1b4dbe508d5727acbe59d913d471f230b970fc0a1e9f974
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 6, 2026.

Transparency log

Release history Release notifications | RSS feed

0.7.4

4 release files

0.7.3

4 release files

0.7.2

4 release files

This release

0.7.1 This release

4 release files

0.7.0

4 release files

0.6.0

4 release files

0.5.5

4 release files

0.5.4

4 release files

0.5.3

4 release files

0.5.2

4 release files

0.5.1

4 release files

0.5.0

4 release files

0.4.0

4 release files

0.3.0

4 release files

0.2.0

4 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page