Skip to main content

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 runs a postinstall step: it creates a starter slopsniff.json if missing, or merges in any new keys from the package template that your file does not yet have (your values are never overwritten). Re-installing or upgrading the package is how you pick up newly documented options without losing custom settings.

{
  "scripts": {
    "slopsniff": "slopsniff .",
    "slopsniff:branch": "slopsniff . --branch",
    "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 .
# only files changed vs main (git diff)
slopsniff . --branch
# 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.
  • Default terminal output is compact and Rich-colored: one summary line (files, issue count, score, status), then findings grouped by directory and file (paths relative to the current working directory when possible). 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

# Only files changed vs main (git diff main --name-only --diff-filter=ACMR, then normal include/exclude rules)
env PYTHONPATH=src uv run python -m slopsniff.cli . --branch
env PYTHONPATH=src uv run python -m slopsniff.cli . --changed-since origin/main

--branch is a shortcut for --changed-since main. The scan root must sit inside a git work tree. Untracked files are not included until they are committed or staged in a way that shows up in that diff (same as plain git diff <ref>).

Terminal vs JSON: default output is human-oriented and grouped as above; pass --format json for CI or tooling. Use --verbose to print each finding’s score contribution.


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-severities": ["low"],
  "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

File and function size (line counts)

These keys control the large-file and large-function rules (line counts include the whole file or function body).

Key Default Effect
max-file-lines-warning 400 At or above this line count → medium large-file finding (only for extensions in large-file-extensions).
max-file-lines-high 800 At or above this line count → high large-file finding.
max-function-lines-warning 50 At or above this line count → medium large-function finding.
max-function-lines-high 100 At or above this line count → high large-function finding.

Raise the warning thresholds when large components or test files are normal for your repo (for example "max-file-lines-warning": 750 so a 592-line file is below the warning cutoff). CLI flags --max-file-lines and --max-function-lines still override only the warning thresholds for a one-off run.

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.
  • exclude-severities is an array of low, medium, and/or high; matching findings are dropped from output and from the total score (case-insensitive).
  • 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).

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 (or, with --changed-since / --branch, only paths from git diff) 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 (compact Rich terminal 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

Metadata

Release files for slopsniff 0.1.19

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for slopsniff 0.1.19
File Size Uploaded Provenance
slopsniff-0.1.19.tar.gz 27.7 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for slopsniff 0.1.19
File Interpreter ABI Platform
slopsniff-0.1.19-py3-none-any.whl Python 3 none any Details

Total release size: 52.2 kB

Release files / slopsniff-0.1.19.tar.gz

Download URL slopsniff-0.1.19.tar.gz
Size 27.7 kB
Tags Source
SHA-256 checksum
How to use checksums
d0990d49648d680a8de8c863704c417fb0e609584045af491b7c5ac9dd48e713
BLAKE2b-256 checksum
How to use checksums
4a4ad64cfa4f32421d628dd2997d6909942b2db87f2930894ebbbee3fb52ea3a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

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 Mar 24, 2026.

Transparency log

Release files / slopsniff-0.1.19-py3-none-any.whl

Download URL slopsniff-0.1.19-py3-none-any.whl
Size 24.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
b066b0e2d43b2ffce3a8ea859439c9d038823156d7c129ca0b9f5f9a4e37c65c
BLAKE2b-256 checksum
How to use checksums
1e108661494a397065fd2997d7c5a92992a6e18cb8d0b9f03028d0453f7a3918
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

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 Mar 24, 2026.

Transparency log

Security for slopsniff 0.1.19

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 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for slopsniff-0.1.19.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 Mar 24, 2026.

Transparency log

A 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.

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