Skip to main content

A lightweight CLI for catching slop in modern codebases before it hardens into tech debt.

Project description

SlopSniff

SlopSniff

PyPI version Python versions CI

A lightweight CLI for catching code-quality drift ("slop") before it hardens into team-wide tech debt.


Quick Start

JavaScript / TypeScript (app repo) Python (app repo)
# One-off run
npx slopsniff-cli .

# or install locally
npm i -D slopsniff-cli

npm i -D slopsniff-cli will create a starter slopsniff.json in your project root if one does not already exist.

{
  "scripts": {
    "slopsniff": "slopsniff .",
    "slopsniff:strict": "slopsniff . --fail-threshold 0 --format json"
  }
}
# install in your Python project
uv add --dev slopsniff
# or: pip install slopsniff
# run in your project
slopsniff .
# optional strict mode
slopsniff . --fail-threshold 0 --format json

Notes:

  • The npm package is a wrapper around the Python CLI.
  • It runs slopsniff via uv tool run --from slopsniff ....
  • On macOS/Linux, it will attempt to install uv automatically if missing.
  • Commit a slopsniff.json in your project root to configure rules and exclusions.

What it catches

Pattern Slop Better
Fallback defaults
Silent primitives that mask missing config
timeout = os.getenv("TIMEOUT", 0)
const retries = process.env.RETRIES || 0;
timeout = require_env("TIMEOUT")
const retries = requireEnv("RETRIES");
Catch-all primitive returns
Flattens every failure into one silent shape
except Exception:
    return []
catch (e) { return null; }
except TimeoutError:
    logger.warning("upstream timeout")
    raise
catch (e) {
  if (e instanceof RateLimitError) { ... }
  throw e;
}
Exposed secrets
Credentials committed in source or docs
API_KEY = "sk-proj-abc123..."
API_KEY = os.environ["API_KEY"]
Large files & functions
Monoliths that resist review and testing
scanner.py — 800+ lines
def do_everything(): — 120 lines
scanner.py — focused orchestrator
def scan(): — delegates to helpers
Duplicate functions
Copy-pasted logic across files
utils.py:  def format_date(d): ...
helpers.py: def format_date(d): ...  # identical
dates.py: def format_date(d): ...  # single source
Helper sprawl
Vague catch-all files and versioned copies
utils.py, helpers.py, common.py
send_email_v2(), format_data_old()
email_service.py, formatters.py
send_email(), format_data()

Local Setup (Open Source Dev)

git clone https://github.com/joshuagilley/slopsniff
cd slopsniff
uv sync --dev
pre-commit install

Quick sanity run:

uv run pytest
uv run ruff check .
env PYTHONPATH=src uv run python -m slopsniff.cli . --fail-threshold 30

Notes:

  • Pre-commit runs ruff, ruff-format, slopsniff, and pytest.
  • Terminal output uses Rich. Use --format json for machine output.
  • For local runs, prefer env PYTHONPATH=src uv run python -m slopsniff.cli ....

Basic Usage

# Scan current directory
env PYTHONPATH=src uv run python -m slopsniff.cli .

# Scan a specific path
env PYTHONPATH=src uv run python -m slopsniff.cli ./src

# JSON output for CI/machines
env PYTHONPATH=src uv run python -m slopsniff.cli . --format json

# Override thresholds ad hoc
env PYTHONPATH=src uv run python -m slopsniff.cli . --max-file-lines 300 --max-function-lines 40

Configuration (slopsniff.json)

SlopSniff auto-loads slopsniff.json from the scan root (the path you pass to slopsniff). You can tune scoring thresholds, file selection, and enabled rules in one place.

Example:

{
  "fail-threshold": 20,
  "max-file-lines-warning": 400,
  "max-file-lines-high": 800,
  "max-function-lines-warning": 50,
  "max-function-lines-high": 100,
  "verbose": false,
  "include-extensions": [".py", ".js", ".ts", ".tsx", ".jsx", ".vue", ".html"],
  "large-file-extensions": [".py", ".js", ".ts", ".tsx", ".jsx", ".vue"],
  "exclude-files": ["temp_slop_examples.py", "src/fixtures/example.py"],
  "exclude-dirs": [".git", "node_modules", ".venv", "tests", "dist", "build"],
  "include": [
    "fallback-defaults",
    "exposed-secrets",
    "large-function",
    "large-file",
    "duplicate-functions",
    "helper-sprawl"
  ]
}

Rule IDs for include:

  • fallback-defaults
  • exposed-secrets
  • large-function
  • large-file
  • duplicate-functions
  • helper-sprawl

Notes:

  • CLI flags still work and override file values (for example, --fail-threshold).
  • If include is omitted, all rules run.
  • exclude-files accepts either bare filenames or scan-root-relative paths.
  • Unknown keys and unknown rule IDs fail fast with clear errors.

Contributing and Commits

Standard flow:

  1. Create a feature branch from main.
  2. Make changes.
  3. Run checks locally:
    uv run pytest
    uv run ruff check .
    env PYTHONPATH=src uv run python -m slopsniff.cli . --fail-threshold 30
    
  4. Commit with a clear message.
  5. Open a PR and merge after CI passes.

Release Process

Publishing is handled by .github/workflows/publish.yml. It runs on GitHub Release published (not on tag push alone).

Recommended: one-command release script

From repo root, on main, with a clean working tree:

./scripts/release.py 0.1.9
# or:
uv run python scripts/release.py v0.1.9

What it does:

  1. Pulls main.
  2. Bumps version in pyproject.toml and src/slopsniff/__init__.py.
  3. Runs uv lock.
  4. Commits chore: release X.Y.Z.
  5. Pushes main.
  6. Tags vX.Y.Z and pushes the tag.
  7. Creates/publishes a GitHub release with gh release create.

Useful flags:

  • --dry-run
  • --no-pull
  • --allow-dirty
  • --notes-file PATH
  • --expect-repo OWNER/REPO

Optional guard:

  • Set SLOPSNIFF_RELEASE_EXPECT_REPO=joshuagilley/slopsniff to prevent accidental release from a fork clone.

If release/publish fails

  • HTTP 400 from PyPI: that version already exists; bump to a new version and release again.
  • HTTP 422 from gh release create: release already exists for that tag. Re-run workflow for that release if needed.
  • Re-running old releases uses the same tagged commit; it does not pick up newer main.

Minimal Architecture Notes

Pipeline:

  1. Walk repo and collect included files.
  2. Parse functions (ast for Python, heuristic parser for JS/TS/TSX/JSX/Vue).
  3. Run per-file rules.
  4. Run cross-file rules.
  5. Aggregate findings and score.
  6. Report (terminal via Rich or json) and exit non-zero on threshold fail.

Key paths:

  • src/slopsniff/cli.py — CLI entrypoint
  • src/slopsniff/scanner.py — orchestration
  • src/slopsniff/rules/ — rule implementations
  • src/slopsniff/reporters/ — terminal/json output
  • scripts/release.py — scripted release flow

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

slopsniff-0.1.16.tar.gz (23.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

slopsniff-0.1.16-py3-none-any.whl (21.7 kB view details)

Uploaded Python 3

File details

Details for the file slopsniff-0.1.16.tar.gz.

File metadata

  • Download URL: slopsniff-0.1.16.tar.gz
  • Upload date:
  • Size: 23.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for slopsniff-0.1.16.tar.gz
Algorithm Hash digest
SHA256 8610e4dfd277ccd4190348ea605746579da366bbc02c7b6e43bd01998ff8fc85
MD5 723a90bfd6886cd252b0a840b873111c
BLAKE2b-256 58c101075236924ff48092130f597b60696a74ff39371a05851ccdea3cbf5816

See more details on using hashes here.

Provenance

The following attestation bundles were made for slopsniff-0.1.16.tar.gz:

Publisher: publish.yml on joshuagilley/slopsniff

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file slopsniff-0.1.16-py3-none-any.whl.

File metadata

  • Download URL: slopsniff-0.1.16-py3-none-any.whl
  • Upload date:
  • Size: 21.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for slopsniff-0.1.16-py3-none-any.whl
Algorithm Hash digest
SHA256 cfc900b95cd89c9121756043dc52a2ad00fe835cfc3ad218b78808c4283383b4
MD5 84826090ff09be64d4196a43beea509d
BLAKE2b-256 feafeb5da16e20f06d7a0b93334fafbf5a9d11affe73f4d09ba754836b7eb8a2

See more details on using hashes here.

Provenance

The following attestation bundles were made for slopsniff-0.1.16-py3-none-any.whl:

Publisher: publish.yml on joshuagilley/slopsniff

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page