smells
Install once. Scan every configured smell. Give humans and coding agents evidence they can audit.
smells is a standalone, deterministic code-smell scanner for Rust, Python, TypeScript, and TSX.
A default smells check runs all 28 active rules across all 23 Refactoring.Guru smell categories. The scanner collects its own bounded evidence, so every repository gets a complete review without building a custom evidence pipeline.
The scanner—not an LLM—decides whether a rule matches. Humans and agents use the resulting locations, measurements, thresholds, source excerpts, and research links to decide what should change.
Quick start · Use cases · Coding agents · Integrate · Reports
What Smells gives you
source + checked-in policy
↓
28 deterministic rules across 23 smell categories
↓
auditable table and JSON evidence
↓
human or coding-agent review grounded in Refactoring.Guru
- Exact repository-relative files, lines, columns, symbols, and source excerpts.
- The observed measurement, comparison, configured threshold, and match status.
- Separate blocking violations and non-blocking review signals.
- An explicit result for every canonical smell, including excluded, inapplicable, and incomplete states.
- A mandatory Refactoring.Guru URL, embedded versioned
when_to_ignoreguidance, and research contract in every matched finding. - Strict source-local suppressions that remain visible and auditable instead of hiding accepted findings.
- Stable JSON for Codex, Claude Code, Pi, CI jobs, hooks, and other automation.
- Fail-closed exit codes when parsing, history, ownership, inputs, or analysis budgets are incomplete.
smells does not execute the repository's application code, builds, tests, package scripts, or compilers.
When to use Smells
| Use case | What Smells contributes |
|---|---|
| AI-assisted code review | Gives the agent deterministic findings instead of asking it to discover smells from scratch |
| Refactoring work | Grounds each proposal in a location, threshold, source excerpt, and required design reference |
| Pull-request CI | Blocks configured violations and invalid or incomplete scans with distinct exit codes |
| Pre-commit review | Scans the complete staged Git snapshot before a commit is created |
| Codebase health audit | Produces a reproducible baseline across all 23 smell categories |
| Monorepo triage | Assigns findings to the nearest Cargo, Python, or Node implementation manifest |
| Team policy | Keeps thresholds, modes, groups, exclusions, and budgets in a reviewed JSON file |
| Higher-fidelity analysis | Accepts optional compiler, coverage, type, test, or architecture evidence per rule |
Use Smells alongside formatting, compilation, type checking, tests, coverage, security scans, and human design review. It complements those checks; it does not replace them.
Supported languages
| Language pack | Files scanned | Active built-in rules | Class-like model |
|---|---|---|---|
rust-v1 |
.rs |
28 | struct/enum state plus inherent and trait impl methods |
python-v1 |
.py, .pyi |
28 | Classes, direct methods, and self/cls field assignments |
typescript-v1 |
.ts, .tsx, .mts, .cts |
28 | Classes, abstract classes, interfaces, signatures, and authored fields |
All three packs cover the same 23 canonical categories. Some categories use more than one rule, which is why each pack contains 28 rules.
| Refactoring.Guru category | Smells covered |
|---|---|
| Bloaters | Long Method, Large Class, Primitive Obsession, Long Parameter List, Data Clumps |
| Object-Orientation Abusers | Alternative Classes with Different Interfaces, Refused Bequest, Switch Statements, Temporary Field |
| Change Preventers | Divergent Change, Parallel Inheritance Hierarchies, Shotgun Surgery |
| Dispensables | Comments, Duplicate Code, Data Class, Dead Code, Lazy Class, Speculative Generality |
| Couplers | Feature Envy, Inappropriate Intimacy, Incomplete Library Class, Message Chains, Middle Man |
Java, Kotlin, JavaScript, and JSX are not currently supported. React and React Native repositories can scan TypeScript and TSX, but not JavaScript, JSX, or native Android source.
Five-minute quick start
1. Install the CLI
Choose one channel. Every channel provides the same Rust executable and rule behavior.
General-purpose installation with uv:
uv tool install smells==0.5.0
smells --version
Inside a Node project:
npm install --save-dev --save-exact @mindful-time/smells@0.5.0
npx --no-install smells --version
With a Rust toolchain:
cargo install --locked --version 0.5.0 smells
smells --version
Prebuilt archives for macOS, Linux, and Windows are available from the v0.5.0 GitHub Release.
2. Add a policy to the repository
Choose the policy that matches the language being scanned:
| Language | Starter policy |
|---|---|
| Rust | examples/quality-policy.json |
| Python | examples/python-quality-policy.json |
| TypeScript/TSX | examples/typescript-quality-policy.json |
For example, add the immutable v0.5.0 Python starter to another repository:
curl -fsSLo quality-policy.json \
https://raw.githubusercontent.com/mindful-time/smells/v0.5.0/examples/python-quality-policy.json
smells contracts validate --policy quality-policy.json
Commit the policy. Local developers, CI, hooks, and coding agents should all use that same reviewed contract.
3. Scan the repository
Save the complete numbered Finding Log for people and agents, and the normalized JSON Evidence Report for tools:
smells check \
--path . \
--policy quality-policy.json \
--format table \
--log smells-findings.log \
--report smells-report.json
This is one scan. Terminal output stays compact and points to the Finding Log Issue Index. smells-findings.log contains every numbered error, blocking, ignored, and review detail; smells-report.json retains normalized measurements, matched findings, immutable guidance, suppression audits, coverage, digests, and ownership.
Use JSON on standard output when another process consumes the report directly:
smells check --path . --policy quality-policy.json --format json
Understand the result
Exit codes
| Exit | Meaning | Required action |
|---|---|---|
0 |
Every selected active required rule completed without an unsuppressed match | Continue; inspect any explicit ignored findings and review-only signals |
1 |
One or more required rules matched | Review the blocking findings |
2 |
The scan was invalid or incomplete | Fix the input, policy, parser, ownership, history, budget, or evidence error |
An exit code of 2 never means “clean.” Errors take precedence over violations, and violations take precedence over a successful result.
Blocking and review findings
| Rule mode | Effect |
|---|---|
required |
A match is a blocking violation and produces exit 1 |
report |
A match is a review signal and does not change an otherwise successful exit |
off |
An explicit custom-policy opt-out; shipped starter policies do not use it |
Each matched finding contains:
- The canonical smell, rule ID, pattern type, and certainty.
- A primary symbol and location plus any related symbols and locations.
- The observed value, match condition, and configured threshold.
- A source excerpt when the rule maps to authored source.
- Why the signal matters and what the reviewer should inspect.
- A behavior-preserving remediation direction.
- The exact Refactoring.Guru URL.
- A non-negotiable instruction to research that page before review or remediation.
checked_no_match_in_measured_scope means the detector ran and stayed within its threshold. It does not claim the code is free of every possible design problem.
incomplete means a prerequisite was unavailable. For example, selected history rules are incomplete outside a Git repository and force exit 2 instead of returning a false clean result.
See the report interface before building a custom parser or integration.
when_to_ignore and source suppressions
Every matched finding resolves and displays its rule pack's immutable when_to_ignore guidance. The guidance, provenance, source URL, checked date, and version are embedded in the installed rule pack, so scans stay offline and deterministic. Consumer policies can select rules and thresholds but cannot rewrite this guidance.
Research the finding's exact URL and inspect its declaration, related locations, callers, and tests before deciding whether the guidance applies. If a match is justified, the only suppression form is a rule-specific source comment with a non-empty reason:
# smells: ignore[python.function_arguments] -- stable external API
@command
def publish(a, b, c, d):
...
// smells: ignore[rust.function_arguments] -- compatibility boundary
#[public_api]
fn publish(a: i32, b: i32, c: i32, d: i32, e: i32, f: i32, g: i32, h: i32) {}
class Publisher {
// smells: ignore[typescript.function_arguments] -- stable external API
@publicApi
publish(a: number, b: number, c: number, d: number): void {}
}
One directive names one exact rule and applies only to the next syntactic declaration; blank lines and decorators/attributes are allowed between them. Multiple rules need separate directives. A multi-location finding can be suppressed only at its primary declaration. Malformed, unknown, duplicate, misplaced, and genuinely unused directives return exit 2.
An accepted match remains in the Finding Log and JSON as ignored_match, including its reason and source location. A scan containing only accepted required findings exits 0 with passed_with_ignored_findings, never a clean verdict. If the targeted rule could not complete, the directive is retained as unverified_due_to_incomplete_rule; Smells does not falsely call it unused, and the incomplete scan still exits 2.
Use Smells with coding agents
Smells needs no MCP server, plugin, model provider, or agent-specific API. The agent runs the CLI, reads the report, researches each finding's exact reference URL, inspects the code, and proposes a behavior-preserving change.
Add a shared agent contract
Put this section in the consuming repository's root AGENTS.md:
## Smell review
- Run `smells check --path . --policy quality-policy.json --format table --log smells-findings.log --report smells-report.json` before reviewing or refactoring code smells.
- Treat exit code 2 as an incomplete scan, never as a clean result.
- Open `smells-findings.log`, start at its Issue Index, and read every referenced error, blocking, ignored, and review detail.
- Treat each finding as evidence to investigate, not proof that a defect exists.
- Before review, remediation, or suppression, open and read the exact reference URL and its `when_to_ignore` guidance.
- NON-NEGOTIABLE RESEARCH: If the URL cannot be consulted, report the research as incomplete and stop review, remediation, and suppression for that finding.
- Verify the complete finding against its declaration, related locations, callers, tests, and repository contracts.
- Add `smells: ignore[exact-rule-id] -- non-empty reason` only when the verified exception applies to the next declaration; never add it merely to pass the hook.
- Prefer small behavior-preserving changes. Run the project's tests and rerun Smells after editing.
Keep the policy and these instructions under version control. The scanner supplies evidence; the agent supplies contextual judgement.
Codex
Codex reads AGENTS.md from the repository root toward its working directory. Put the shared contract at the root. Add narrower overrides only where needed.
Ask Codex:
Run the repository's Smells scan. Explain the exit code and prioritize blocking findings.
For every finding you review, follow its reference_check and read the exact
Refactoring.Guru URL first. Do not edit code until the evidence is verified against
the repository. Then propose the smallest behavior-preserving change and test it.
Claude Code
Current Claude Code versions can read AGENTS.md. If the project already has a CLAUDE.md, add this import so one contract remains authoritative:
@AGENTS.md
Claude Code documents this pattern in Share one file with other coding tools.
Ask Claude Code the same review prompt. The contract and JSON schema do not change by model or provider.
Pi
Pi loads AGENTS.md or CLAUDE.md from the project path. Restart Pi or run /reload after changing the instructions.
Start Pi in the repository, then ask it to run the shared Smells workflow:
Use the repository Smell review instructions. Run the scan, research each matched
finding's exact reference URL, and return a prioritized review before changing code.
Other coding agents
Place the shared contract in the tool's project-instruction file or include it in the task prompt. The only required capabilities are running a local command, reading JSON and source, and opening the emitted HTTPS reference URL.
Do not ask an agent to “fix every smell.” Ask it to verify findings, explain tradeoffs, preserve behavior, test each change, and rerun the deterministic scan.
Integrate Smells into a repository
Keep one source of truth
A typical single-language integration contains:
quality-policy.json reviewed rules, modes, thresholds, exclusions, and budgets
AGENTS.md shared human/agent review contract
CLAUDE.md optional `@AGENTS.md` import
smells-report.json generated report; usually ignored by Git
smells-findings.log generated numbered agent log; usually ignored by Git
Pin the Smells version in the installation command. Upgrade the executable and policy contract deliberately, then review the resulting report changes.
Run it in CI
Add the same command to the repository's existing quality job:
uvx --from smells==0.5.0 smells check \
--path . \
--policy quality-policy.json \
--format table \
--log smells-findings.log \
--report smells-report.json
For GitHub Actions, check out full history with fetch-depth: 0. Divergent Change and Shotgun Surgery use up to 200 commits of bounded Git co-change history.
Preserve both artifacts. Let exit 1 block configured violations and exit 2 block incomplete analysis.
Run it before commits
--staged scans the complete tracked source snapshot in the Git index. It reads source, runtime manifests, policy, and optional evidence from staged blobs rather than unstaged replacements.
With the Python pre-commit framework:
repos:
- repo: local
hooks:
- id: smells
name: deterministic smell scan
language: system
entry: uvx --from smells==0.5.0 smells check --staged --policy quality-policy.json --format table --log smells-findings.log --report smells-report.json
pass_filenames: false
Node projects can replace the entry with npx --no-install smells check .... Rust development images can install Smells once and use smells check ... directly.
The repository also includes an inactive shell hook example. Merge it into an existing hook instead of overwriting other quality checks.
Scan a monorepo
Run from the monorepo root. Each source file is assigned to its nearest Cargo.toml, pyproject.toml, or package.json, and each implementation receives a separate report section.
Nested manifests override parent ownership. Source without a matching manifest stays visible under __unowned__.
A mixed-language repository needs one checked-in policy and one invocation per language pack:
smells check --path . --policy rust-quality-policy.json --format json
smells check --path . --policy python-quality-policy.json --format json
smells check --path . --policy typescript-quality-policy.json --format json
Keep the reports separate so their language-pack identities and thresholds remain explicit.
How the default scan works
Every starter policy selects all. A normal scan runs all 28 rules and returns one explicit result for each of the 23 canonical smells.
Built-in collectors cover:
- Function size, arguments, class/type size, fields, and method lines.
- Data clumps, duplicate callable bodies, comment density, data classes, and tiny classes.
- Primitive-heavy state, repeated dispatch, temporary fields, forwarding types, and alternative interfaces.
- Inheritance rejection, interface conformance, private-member access, message chains, and extension workarounds.
- Conservative zero-coverage CRAP bounds, unused private declarations, and unused generic parameters.
- Up to 200 Git commits of co-change history for Divergent Change and Shotgun Surgery.
These are bounded structural indicators, not proof of a design defect. The report names each collector, certainty, scope, and limitation so a reviewer can judge the evidence honestly.
Configure what runs
The checked-in policy owns the language pack, rule modes, thresholds, exclusions, default groups, and analysis budgets.
Inspect the resolved selection before scanning:
smells policy show --policy quality-policy.json --format table
smells rules --rule-pack python-v1
Available groups are all, source, evidence, and the five canonical smell categories. Starter policies select all; users must make any narrower selection intentionally.
| Selector | Effect |
|---|---|
--group NAME |
Add a group to configured defaults |
--only-group NAME |
Replace defaults with only the named groups |
--all-groups |
Select every rule |
--no-default-groups |
Start without configured defaults |
--no-group NAME |
Remove a group after inclusions; exclusions win |
Unknown, duplicate, or conflicting selectors return exit 2. The resolved selection and selected/excluded rules appear in every report.
Optional higher-fidelity evidence
--evidence is not required for a complete default scan. It is an optional replacement for individual built-in observations when a team already has stronger compiler, type, coverage, test, or architecture facts.
smells check \
--path . \
--policy quality-policy.json \
--evidence provider-evidence.json \
--format json
Rules omitted from the evidence bundle continue using built-in collectors. Supplied evidence must match the exact input digest and fails closed when stale, malformed, duplicated, or incomplete.
Start with the provider evidence guide and JSON Schema.
Safety and limitations
- Smells captures source and fixed runtime manifests without following symlinks.
- It never executes repository code, dependencies, build scripts, tests, compilers, or package managers.
- Deterministic analysis budgets prevent unbounded work; exhaustion returns exit
2. - Missing Git history, unresolved ownership, parser failure, and invalid evidence are incomplete—not clean.
- Rust scans authored branches, including inactive
cfgsource; Python and TypeScript scan authored source text. - Built-in type, coverage, and architecture indicators do not claim compiler-level or runtime certainty.
- The scanner reports evidence but never edits or automatically refactors source.
Budget errors identify the incomplete rule and include both the charged work and policy limit directly in the Finding Log, for example:
rule python.duplicate_functions: maximum_pairs budget exceeded: charged 250001 exact comparisons; policy limit is 250000
The result remains incomplete_due_to_errors; any suppression targeting that incomplete rule remains unverified rather than being misreported as unused.
Run the project's compiler, type checker, tests, fresh coverage, and security tooling alongside Smells.
Installation and release provenance
| Channel | Package | Installation |
|---|---|---|
| PyPI | smells |
uv tool install smells==0.5.0 |
| npm | @mindful-time/smells |
npm install --save-dev --save-exact @mindful-time/smells@0.5.0 |
| crates.io | smells |
cargo install --locked --version 0.5.0 smells |
| GitHub | v0.5.0 release | Download the archive for the host platform |
All registry payloads originate from one immutable GitHub Release. Protected workflows verify its commit, attestations, checksums, and exact assets before publishing through short-lived identities.
See package distribution and the release process for platform packages, provenance verification, SBOMs, and recovery guarantees.
Reference documentation
- Project integration
- Report interface
- Provider evidence
- Rust rule contracts
- Python rule contracts
- TypeScript rule contracts
- Performance and algorithm research
- Repository governance
Developing Smells
Run the deterministic local gate:
make pre-commit-push
It runs formatting, build, Clippy, the offline test suite, the scanner against itself, CRAP analysis, Gitleaks, and OSV. Pull requests run the same fail-closed gate and must originate from a fork.
The optional live test exercises the complete finding-to-Refactoring.Guru research path:
cargo test --locked --test e2e_live -- --ignored --nocapture
Project status
Version 0.5.0 is prepared for GitHub Releases, PyPI, npm, GitHub Packages, and crates.io. Smells is open-source software licensed under the MIT License.
Repository: mindful-time/smells
Release files for smells 0.5.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| smells-0.5.0-py3-none-win_amd64.whl | Python 3 | none | Windows x86-64 | Details |
| smells-0.5.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl | Python 3 | none | Linux glibc 2.17+ x86-64 | Details |
| smells-0.5.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl | Python 3 | none | Linux glibc 2.17+ ARM64 | Details |
| smells-0.5.0-py3-none-macosx_11_0_arm64.whl | Python 3 | none | macOS 11.0+ ARM64 | Details |
| smells-0.5.0-py3-none-macosx_10_12_x86_64.whl | Python 3 | none | macOS 10.12+ x86-64 | Details |
Total release size: 15.2 MB
Release files / smells-0.5.0-py3-none-win_amd64.whl
| Download URL | smells-0.5.0-py3-none-win_amd64.whl |
|---|---|
| Size | 3.2 MB |
| Tags | Python 3 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
b0c85593b72e81718997450522384597826311687b02a4dae5e55ceb558fe7b4
|
|
BLAKE2b-256 checksum How to use checksums |
946f969fc6e2fa0f703cca53044802e514de73950812ff1503fde6daaf46981a
|
| 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 25, 2026.
Transparency logRelease files / smells-0.5.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | smells-0.5.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 3.2 MB |
| Tags | Linux glibc 2.17+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
e4263121756c669981b514df72c90f17d5906f25129153b3bab0aec95b15dc94
|
|
BLAKE2b-256 checksum How to use checksums |
45f48883d21897042af6a5d5ed9c3be67eb9cdcdd47098e4401c1e7586b07073
|
| 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 25, 2026.
Transparency logRelease files / smells-0.5.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
| Download URL | smells-0.5.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl |
|---|---|
| Size | 2.9 MB |
| Tags | Linux glibc 2.17+ ARM64 Python 3 |
|
SHA-256 checksum How to use checksums |
521432ad82354cdd305e7e306b6a89c655e564d5b3ac52c8c2a2d5af8c4003b9
|
|
BLAKE2b-256 checksum How to use checksums |
18c15394bef24350dff88e7bf19329046f870e2ef452a05e52b454105b0f3183
|
| 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 25, 2026.
Transparency logRelease files / smells-0.5.0-py3-none-macosx_11_0_arm64.whl
| Download URL | smells-0.5.0-py3-none-macosx_11_0_arm64.whl |
|---|---|
| Size | 2.9 MB |
| Tags | Python 3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
5647cedfb13667b433ca6f68dcce4b6c7cec3bfc36728e09029f8f4c5bba38c9
|
|
BLAKE2b-256 checksum How to use checksums |
bcfa0dffe47d37c160c375679fb4743f67683e81b41b5a490ac1a46eb2b90b86
|
| 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 25, 2026.
Transparency logRelease files / smells-0.5.0-py3-none-macosx_10_12_x86_64.whl
| Download URL | smells-0.5.0-py3-none-macosx_10_12_x86_64.whl |
|---|---|
| Size | 3.1 MB |
| Tags | Python 3 macOS 10.12+ x86-64 |
|
SHA-256 checksum How to use checksums |
51eefa5fe2db0f373525de8552a52347143dc67f3256f3866b8d9cfab37d7a7b
|
|
BLAKE2b-256 checksum How to use checksums |
9a0bba58fa3789ad39d0fc684fb907294b5c8f28703ae8af7180ad2a50ffca86
|
| 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 25, 2026.
Transparency log