pycoati
Audits Python test suites for mock smells and suspicious tests. Walks a project
with tree-sitter, optionally runs pytest for collection, durations, and
coverage, and emits a structured inventory: per-file and per-test counts,
mock-API smells, and a per-test suspicion score.
Install
# From source (Rust)
cargo install --path .
# Or as a Python package (via maturin)
maturin develop
Usage
pycoati --help
pycoati <PATH>
Audit a project directory or a single .py file. Writes the inventory to
stdout as JSON unless --output is given.
# Structured inventory to a file
pycoati . --output inventory.json
# Aligned-column terminal view
pycoati . --format pretty
# Static analysis only, no pytest subprocesses
pycoati . --static-only
Key flags: --format <json|pretty>, --output <PATH>, --tests-dir <PATH>,
--static-only, --no-coverage, --top-suspicious <N>,
--project-package <NAME>, --python <CMD>, --pytest-args <STR>,
--member-cwd <root|member>, --accept-file <PATH>, --no-accept,
--include-accepted. See pycoati --help for the full list.
Weights behind suspicion_score are documented in WEIGHTS.md.
Accepted findings
An audit that runs on a schedule keeps re-surfacing the same legitimate tests.
Record the review decision once, with a reason, in .pycoati-accept.toml at
the project root (a single-file scan walks up from the file to find it):
schema_version = "1"
[[accept]]
test = "tests/test_packaging.py::test_wheel_installs"
signals = ["zero_asserts", "high_setup_ratio"]
reason = "assertions run in a child interpreter; the parent propagates failure via subprocess.run(check=True)"
reviewed = "2026-09-12"
fingerprint = "3f0a1c7d9b2e4a56" # optional: lapses the entry if the test is edited
reason is required — an entry without one is a parse error. Acceptance is
scoped to one test and one named signal (mock_only_assertions,
mock_overuse, zero_asserts, high_setup_ratio); there is no wildcard, so
a new finding on an accepted test still surfaces.
The accepted test keeps running and keeps counting: pytest arguments are
untouched, so suite.test_count, runtime and coverage are unchanged, and every
count, smell hit and score stays in the inventory as measured. The only effect
is that a test whose every active signal is accepted is held back from
top_suspicious.test_functions. The decisions and their reasons are reported
under accepted.findings, entries that no longer apply under accepted.stale,
and --include-accepted puts everything back on the shortlist for a full
report.
Run pycoati guide setup for the full reference.
pycoati guide [PAGE]
Prints the workflow instructions that ship inside the binary, aimed at a coding agent working through an audit. Because the pages are embedded at compile time, they always describe the inventory schema this exact build emits.
# Pick the page from the state of the current directory
pycoati guide
# Or ask for one by name
pycoati guide setup
pycoati guide analyze
pycoati guide remediate
| Page | Contents |
|---|---|
setup |
Configuration surface, how to run the scan, where inventory.json lands. |
analyze |
Field reference for every key in the inventory, plus the anti-pattern taxonomy and its detection heuristics. |
remediate |
The remedy ladder, the human-approval gate, and the "do not" list. |
With no argument, the page is chosen from the current directory: analyze when
an inventory.json is present there, otherwise setup. remediate is never
auto-selected — reach it via the analyze page's breadcrumb or by name.
The subcommand shadows a directory of the same name: if you have a ./guide
directory to audit, write pycoati ./guide.
Each page ends with a single next: line, so an agent is always one command
away from the right instructions. A --format pretty scan that finds candidates
ends with the same breadcrumb; JSON output never carries it.
The page sources are plain markdown under docs/guide/, embedded
via include_str!. tests/guide_schema_drift.rs asserts that every field name
serde emits is documented on the analyze page, so a schema change cannot land
without the guide being updated alongside it.
Development
# Pre-commit checks
cargo fmt --check && cargo clippy --all-targets --all-features -- -D warnings && cargo test --all-features
# Full review (fmt, clippy, tests, audit, deny)
make review
License
MIT
Metadata
Release files for pycoati 0.2.9
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| pycoati-0.2.9.tar.gz | 195.5 kB | With provenance | Details |
Built distributions (wheels)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pycoati-0.2.9-py3-none-win_amd64.whl | Python 3 | none | Windows x86-64 | Details |
| pycoati-0.2.9-py3-none-manylinux_2_28_x86_64.whl | Python 3 | none | Linux glibc 2.28+ x86-64 | Details |
| pycoati-0.2.9-py3-none-macosx_11_0_arm64.whl | Python 3 | none | macOS 11.0+ ARM64 | Details |
Total release size: 4.8 MB
Release files / pycoati-0.2.9.tar.gz
| Download URL | pycoati-0.2.9.tar.gz |
|---|---|
| Size | 195.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
60a264c128d9d2ad24d56aea626f260a5dfa2e55f21513e82337f75499af8b26
|
|
BLAKE2b-256 checksum How to use checksums |
7c9332fd410e64d83ee056c7c3fc366a2cf20a405ce6cbf3247cf2929cdf93a5
|
| 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 13, 2026.
Transparency logRelease files / pycoati-0.2.9-py3-none-win_amd64.whl
| Download URL | pycoati-0.2.9-py3-none-win_amd64.whl |
|---|---|
| Size | 1.5 MB |
| Tags | Python 3 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
f92a7abf3c9340382ae8234c055255aaa1ac9f8a260fe3524e7a81e8d308851a
|
|
BLAKE2b-256 checksum How to use checksums |
cc6e3fa92cbb39070e6c754f630a4c0d9d38c5f61087bea6db05a05cf5c9b5c6
|
| 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 13, 2026.
Transparency logRelease files / pycoati-0.2.9-py3-none-manylinux_2_28_x86_64.whl
| Download URL | pycoati-0.2.9-py3-none-manylinux_2_28_x86_64.whl |
|---|---|
| Size | 1.7 MB |
| Tags | Linux glibc 2.28+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
660281ced42b42c304dd88d208ac361c89d5a783560d69d6560a88f0be300e0a
|
|
BLAKE2b-256 checksum How to use checksums |
1277b267cb4210bf452d5c3b095d1614a2d038aa7da3d5a9afe83d3e2d88a34c
|
| 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 13, 2026.
Transparency logRelease files / pycoati-0.2.9-py3-none-macosx_11_0_arm64.whl
| Download URL | pycoati-0.2.9-py3-none-macosx_11_0_arm64.whl |
|---|---|
| Size | 1.4 MB |
| Tags | Python 3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
8abd3c21280d4087e2a401f563b1eaf18946ae09eff78823e8b6ce36da88f346
|
|
BLAKE2b-256 checksum How to use checksums |
e43cca527e44b8b282f9bed71923bc44ddd5d975a2a29f42a8a81f4e8a79decc
|
| 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 13, 2026.
Transparency logSecurity for pycoati 0.2.9
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.
Full provenance
All 4 files in this release have been uploaded with provenance metadata.
Attestation for pycoati-0.2.9.tar.gz
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 13, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.