Skip to main content

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_ignore guidance, 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 cfg source; 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

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)

Table of built distributions (wheels) for smells 0.5.0
File
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 log

Release 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 log

Release 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 log

Release 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 log

Release 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

Release history Release notifications | RSS feed

This release

0.5.0 This release

5 release files

0.4.0

5 release files

0.3.0

5 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