codeecho
A developer tool that scans your codebase to detect and highlight echoes of duplicated or near-duplicated code, so you can refactor toward cleaner, more maintainable designs.
Prerequisites
- Python
>=3.14
Installation
pip install codeecho
Usage
python -m codeecho <path> [options]
Clone detection levels
| Type | Detection strategy | Example |
|---|---|---|
| Type-1 | Exact copy-paste — identical token sequences | Two functions with the same code copied verbatim |
| Type-2 | Structural clone — same structure, renamed identifiers / literals | Same logic with different variable names |
| Type-3 | Near-duplicate — high Jaccard similarity on token sets | Nearly identical functions with a few extra lines |
Supported languages
| Language | Extensions | Grammar |
|---|---|---|
| Python | .py |
tree-sitter-python |
| JavaScript | .js, .mjs, .cjs |
tree-sitter-javascript |
| TypeScript | .ts, .tsx |
tree-sitter-typescript |
| Java | .java |
tree-sitter-java |
| Go | .go |
tree-sitter-go |
| Gosu | .gs, .gsx |
tree-sitter-gosu |
Arguments
| Argument | Description |
|---|---|
path |
Root directory to scan. |
Options
| Option | Default | Description |
|---|---|---|
--types <types> |
all |
Clone types to detect: comma-separated (1, 2, 3) or all. |
--threshold <float> |
0.8 |
Jaccard similarity threshold for Type-3 detection (0.0–1.0). |
--output <name> |
codeecho-output |
Base name (without extension) for output file(s). |
--output-dir <dir> |
<cwd>/reports |
Directory where output file(s) will be written. |
--db-dir <dir> |
~/.codeecho |
Directory for the SQLite scratch database (codeecho.db). Session records are removed after the report is written. |
--format <fmt> |
both |
Output format: json, html, or both. |
--min-tokens <n> |
10 |
Minimum token count for a code fragment to be included. |
--exclude <pattern> |
(none) | Glob pattern(s) to exclude from scanning (repeatable). |
--target-list |
false |
Treat PATH as a single existing file listing target paths (files and/or directories), one per line, instead of individual PATH arguments. Blank lines and lines starting with # are skipped. |
--basis <file> |
(none) | File listing basis target paths, one per line — same format as --target-list. Absolute file/directory entries are merged into the scan automatically and matched exactly; relative entries (e.g. a bare filename like Foo.gs) are matched by filename/suffix against any file discovered in the scan. The report is filtered to only clone groups touching at least one basis file, and groups duplicated purely among basis files are flagged (basis_internal in JSON, a "Basis-to-Basis" badge in HTML). |
--version |
Print the version and exit. | |
-h, --help |
Show help and exit. |
Examples
Scan the current directory and write both JSON and HTML reports:
python -m codeecho .
Detect only Type-1 and Type-2 clones in a src/ tree:
python -m codeecho src --types 1,2
Scan with a custom output name and directory:
python -m codeecho . --output my-scan --output-dir audit/reports
Lower the Type-3 threshold to catch more near-duplicates:
python -m codeecho . --threshold 0.6
Exclude test and vendor directories:
python -m codeecho . --exclude "*/tests/*" --exclude "*/vendor/*"
Scan targets listed in a file, one path per line:
python -m codeecho targets.txt --target-list
Find where code from a reference/basis set of files is duplicated elsewhere in the codebase:
python -m codeecho src --basis basis.txt
Configuration
| Environment variable | Description |
|---|---|
CODEECHO_CONFIG_DIR |
Directory where logging.ini, .ignore, and config.ini are seeded on first run. When unset, the bundled copies inside the package are used directly. |
.ignore file
On first run, a .ignore file is seeded into CODEECHO_CONFIG_DIR (or the package directory when unset). It follows gitignore syntax and is applied during file scanning to exclude paths in addition to any --exclude patterns passed on the command line. Edit this file to permanently suppress paths you never want scanned.
Overriding the ignore filename
config.ini (also seeded into CODEECHO_CONFIG_DIR on first run) contains an [override] section with an ignore-file key, which names the file used in place of .ignore, resolved relative to CODEECHO_CONFIG_DIR:
[override]
ignore-file = .ignore
Point ignore-file at a different filename to use an alternate ignore file (also placed inside CODEECHO_CONFIG_DIR). If the configured file is missing, codeecho logs a warning and falls back to the bundled .ignore.
Development
Prerequisites
- Poetry
2.2+
Setup
poetry install
Run
poetry run python -m codeecho <path> [options]
Architecture
flowchart TD
CLI["__main__.py\n(Click CLI)"] --> ScannerM["scanner.py\nFile discovery"]
ScannerM --> Parser["parser.py\nTree-sitter parsing"]
Parser --> Extractor["extractor.py\nFragment extraction\n(functions · classes · files)"]
Extractor --> Normalizer["normalizer.py\nRegex tokeniser\nType-2 normalisation"]
Normalizer --> Fingerprint["fingerprint.py\nSHA-256 hashing"]
Fingerprint --> DB["db.py\nSQLite session store"]
DB --> Detector["detector.py\nType-1 / 2 hash grouping\nType-3 Jaccard similarity"]
Detector --> DB
DB --> JSON["reporter/json_reporter.py\nJSON report"]
DB --> HTML["reporter/html_reporter.py\nHTML report"]
DB -->|delete session| Cleanup["Session cleanup"]
Format and Lint
poetry run black codeecho
poetry run pylint codeecho
Run Tests with Coverage
poetry run pytest --cov=codeecho tests --cov-report html
Changelog
License
This project is licensed under the MIT License.
Author
Ron Webb
Release files for codeecho 1.2.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| codeecho-1.2.1.tar.gz | 31.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| codeecho-1.2.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 67.0 kB
Release files / codeecho-1.2.1.tar.gz
| Download URL | codeecho-1.2.1.tar.gz |
|---|---|
| Size | 31.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5a6948c0d771a04c7cc5813dac14c5740a6ae29db5b1ef615e3759781fa075da
|
|
BLAKE2b-256 checksum How to use checksums |
984488e8c7c6243155ce67b50654ecdbeedf48dd97b296565e1cea8882a2d18e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
poetry/2.2.0 CPython/3.14.7 Linux/6.17.0-1022-azure
|
Release files / codeecho-1.2.1-py3-none-any.whl
| Download URL | codeecho-1.2.1-py3-none-any.whl |
|---|---|
| Size | 35.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8cee02ba9d152c940874c427810db13daab1ee55cde484d057c9dc986fdd5e9b
|
|
BLAKE2b-256 checksum How to use checksums |
10ab2ec093a23ee365a8c88290587db2c8fd58b00f8911b03b4e2757ab98ab84
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
poetry/2.2.0 CPython/3.14.7 Linux/6.17.0-1022-azure
|