Skip to main content

Quality gates for AI-assisted codebases โ€” catch the slop LLMs leave behind.

Project description

๐Ÿชฃ Slop-Mop

PyPI version CI Python 3.10+ License

Automated steering for AI-generated code "shipping" to production. Leverage the very behavior that results in slop to clean it up. Give your AI a mop and weaponize it in an attempt to keep your repos seaworthy

Slop-Mop

AI agents are captaining a lot of ships these days. As a group, they are great naval tacticians in battle, but horrible at maintaining their ships. They close tickets, ship features, pass tests โ€” and leave behind duplicated code, untested paths, creeping complexity, and security gaps. Nobody intends to create this mess. It's a natural byproduct of accomplishing tasks, and without something to catch it, it accumulates until the codebase becomes unnavigable.

The useful thing is that every AI agent makes the same kinds of mistakes. They're overconfident (code compiles, must be correct), deceptive (tests pass, must be tested), lazy (it works, no need to clean up), and myopic (this file is fine, never mind what it duplicates). These failure modes are predictable, which means they're automatable.

Slop-mop runs a set of quality gates organized around these four failure modes. Each gate targets a specific pattern โ€” bogus tests, dead code, duplicated strings, complexity creep, missing coverage โ€” and when one fails, it tells the agent exactly what's wrong and how to fix it. Two levels:

  • Swab (sm swab) โ€” routine maintenance, every commit. Quick checks that keep things from getting worse.
  • Scour (sm scour) โ€” deep inspection before opening a PR, or coming into port. Catches what routine swabbing misses and gives you a chance to clear the barnacles from the hull

The mop finds the slop. The agent cleans it up. The ship stays seaworthy.


Quick Start

# Install (once per machine)
pipx install slopmop          # recommended โ€” isolated, no dep conflicts
# or: pip install slopmop

# Set up the project
sm init                       # auto-detects languages, writes .sb_config.json

# Run quality gates
sm swab                       # fix what it finds, commit when green
sm scour                      # thorough check before opening a PR

sm init auto-detects Python, JavaScript, or both and writes a .sb_config.json with applicable gates enabled.


The Loop

Development with slop-mop follows a single repeated cycle:

sm swab โ†’ see what fails โ†’ fix it โ†’ repeat โ†’ commit

When a gate fails, the output tells the agent exactly what to do next:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ ๐Ÿค– AI AGENT ITERATION GUIDANCE                           โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ Level: swab                                              โ”‚
โ”‚ Failed Gate: overconfidence:coverage-gaps.py             โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ NEXT STEPS:                                              โ”‚
โ”‚                                                          โ”‚
โ”‚ 1. Fix the issue described above                         โ”‚
โ”‚ 2. Re-check: sm swab -g overconfidence:coverage-gaps.py  โ”‚
โ”‚ 3. Resume:   sm swab                                     โ”‚
โ”‚                                                          โ”‚
โ”‚ Keep iterating until all the slop is mopped.             โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

This is purpose-built for AI agents. The guidance is token-optimized and machine-readable, the iteration is mechanical, and the agent never has to wonder what to do next. The same trait that creates slop โ€” relentless task accomplishment โ€” is what makes agents excellent at cleaning it up when given precise instructions. Slop-mop turns the agent's biggest liability into its best feature: point the mop at the mess, and the agent won't stop until it's clean.

Use sm status for a report card of all gates at once.


Why These Categories?

Gates aren't organized by language โ€” they're organized by the failure mode they catch. These are the four ways LLMs reliably degrade a codebase:

๐Ÿ”ด Overconfidence

"It compiles, therefore it's correct and will work perfectly in production"

The LLM generates code that looks right, passes a syntax check, and silently breaks at runtime. These gates verify that the code actually works.

Gate What It Does
overconfidence:coverage-gaps.js ๐Ÿ“Š JavaScript coverage analysis
overconfidence:coverage-gaps.py ๐Ÿ“Š Whole-repo coverage (80% default threshold)
overconfidence:missing-annotations.py ๐Ÿ” mypy strict โ€” types must check out
overconfidence:type-blindness.js ๐Ÿ—๏ธ TypeScript type checking (tsc)
overconfidence:type-blindness.py ๐Ÿ”ฌ pyright strict โ€” second opinion on types
overconfidence:untested-code.js ๐Ÿงช Jest test execution
overconfidence:untested-code.py ๐Ÿงช Runs pytest โ€” code must actually pass its tests

๐ŸŸก Deceptiveness

"These tests are in the way of closing the ticket - how can I get around them?"

The LLM writes tests that assert nothing, mock everything, or cover the happy path and call it done. Coverage numbers look great. The code is still broken.

Gate What It Does
deceptiveness:bogus-tests.js ๐ŸŽญ Bogus test detection for JS/TS
deceptiveness:bogus-tests.py ๐ŸงŸ AST analysis for tests that assert nothing
deceptiveness:gate-dodging ๐ŸŽญ Detects loosened quality thresholds
deceptiveness:hand-wavy-tests.js ๐Ÿ” ESLint expect-expect assertion enforcement

๐ŸŸ  Laziness

"When I ran mypy, it returned errors unrelated to my code changes..."

The LLM solves the immediate problem and moves on. Formatting is inconsistent, dead code accumulates, complexity creeps upward, and nobody notices until the codebase is incomprehensible.

Gate What It Does
laziness:broken-templates.py ๐Ÿ“„ Jinja2 template validation
laziness:complexity-creep.py ๐ŸŒ€ Cyclomatic complexity (max rank C)
laziness:dead-code.py ๐Ÿ’€ Dead code detection via vulture (โ‰ฅ80% confidence)
laziness:silenced-gates ๐Ÿ”‡ Detects disabled gates when language tooling exists
laziness:sloppy-formatting.js ๐ŸŽจ ESLint + Prettier (supports auto-fix ๐Ÿ”ง)
laziness:sloppy-formatting.py ๐ŸŽจ autoflake, black, isort, flake8 (supports auto-fix ๐Ÿ”ง)
laziness:sloppy-frontend.js โšก Quick ESLint frontend check
laziness:stale-docs ๐Ÿ“– Detects stale README gate tables

๐Ÿ”ต Myopia

"This file is fine in isolation โ€” I don't need to see what it duplicates three directories away"

The LLM has a 200k-token context window and still manages tunnel vision. It duplicates code across files, ignores security implications, and lets functions grow unbounded because it can't see the pattern.

Gate What It Does
myopia:code-sprawl ๐Ÿ“ File and function length limits
myopia:dependency-risk.py ๐Ÿ”’ Full security audit (code + pip-audit)
myopia:just-this-once.py ๐Ÿ“ˆ Coverage on changed lines only (diff-cover)
myopia:source-duplication ๐Ÿ“‹ Code clone detection (jscpd)
myopia:string-duplication.py ๐Ÿ”ค Duplicate string literal detection
myopia:vulnerability-blindness.py ๐Ÿ” bandit + semgrep + detect-secrets

PR Gates

Gate What It Does
pr:ignored-feedback ๐Ÿ’ฌ Checks for unresolved PR review threads

Levels

Every gate has an intrinsic level โ€” the point in the workflow where it belongs:

Level Command Gates When to Use
Swab sm swab All overconfidence, deceptiveness, laziness, myopia checks Before every commit
Scour sm scour Everything in swab + PR comments, diff-coverage, full security audit Before opening or updating a PR

Scour is a strict superset of swab โ€” it runs everything swab does, plus context-dependent gates that need a PR, deeper analysis, or more execution time.

Individual gates can be run directly with -g:

sm swab -g overconfidence:coverage-gaps.py     # re-check just coverage
sm swab -g laziness:complexity-creep.py        # re-check just complexity

Time Budget

Use --swabbing-time to set a time budget in seconds. Gates with historical runtime data are sorted fastest-first and skipped once the accumulated estimate would exceed the budget. Gates without timing history always run (to establish a baseline). Once a gate starts running, it runs to completion.

sm swab --swabbing-time 30    # only run gates that fit in ~30 seconds

sm init sets a default of 20 seconds. Change it any time:

sm config --swabbing-time 45  # raise the budget
sm config --swabbing-time 0   # disable the limit entirely

Time budgets only apply to swab. Scour runs always execute every gate.


Getting Started

Most projects won't pass all gates on day one. That's expected.

1. Initialize

sm init                       # auto-detects everything, writes .sb_config.json

2. See What Fails

sm swab                       # run swab-level gates, see what fails
sm status                     # full report card

3. Disable What's Not Ready Yet

sm config --disable laziness:complexity-creep.py     # too many complex functions right now
sm config --disable overconfidence:coverage-gaps.py  # coverage is at 30%, not 80%
sm swab                                        # get the rest green first

4. Fix Everything That's Left

Iterate: run sm swab, fix a failure, run again. The iteration guidance tells the agent exactly what to do after each failure.

5. Install Hooks

sm commit-hooks install           # pre-commit hook runs sm swab
sm commit-hooks status            # verify hooks are installed

Now every git commit runs slop-mop. Failed gates block the commit.

6. Re-enable Gates Over Time

sm config --enable laziness:complexity-creep.py      # refactored enough, turn it on
sm config --enable overconfidence:coverage-gaps.py   # coverage is at 75%, set threshold to 70

With hooks in place, every commit runs through slop-mop. Gates that aren't ready yet stay disabled until the codebase catches up.


Configuration

sm config --show              # show all gates and their status
sm config --enable <gate>     # enable a disabled gate
sm config --disable <gate>    # disable a gate
sm config --json <file>       # bulk update from JSON

Include / Exclude Directories

sm config --exclude-dir myopia:generated       # skip generated code
sm config --include-dir overconfidence:src      # only check src/
  • include_dirs: whitelist โ€” only these dirs are scanned
  • exclude_dirs: blacklist โ€” always skipped, takes precedence

.sb_config.json

Edit directly for per-gate configuration:

{
  "version": "1.0",
  "python": {
    "gates": {
      "coverage": { "threshold": 80 },
      "tests": { "test_dirs": ["tests"] }
    }
  },
  "quality": {
    "exclude_dirs": ["generated", "vendor"]
  }
}

CI Integration

GitHub Actions

name: slop-mop
on:
  pull_request:
    branches: [main]
  push:
    branches: [main]

jobs:
  quality-gates:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: actions/setup-python@v5
        with:
          python-version: '3.11'
      - run: pip install slopmop
      - run: sm swab
      - if: github.event_name == 'pull_request'
        env:
          GH_TOKEN: ${{ github.token }}
        run: sm scour

Check CI Status Locally

sm ci               # current PR
sm ci 42             # specific PR
sm ci --watch        # poll until CI completes

Architecture

Slop-mop installs as a normal package and is configured per-project via .sb_config.json. The sm command goes on PATH once and works in any repo.

Tool resolution order โ€” sm uses the project's tools when available:

  1. <project_root>/venv/bin/<tool> or .venv/bin/<tool> โ€” project-local venv (highest priority)
  2. $VIRTUAL_ENV/bin/<tool> โ€” currently activated venv
  3. System PATH โ€” sm's own bundled tools (via pipx)

This means if the project has its own pytest (with plugins like pytest-django), sm uses it. Otherwise, sm falls back to its own.

Submodule alternative: For strict version pinning, add slop-mop as a git submodule and invoke python -m slopmop.sm directly. Supported but not recommended for most projects.


Development

# Working on slop-mop itself
pip install -e .
sm scour                   # dogfooding โ€” sm validates its own code
pytest

See CONTRIBUTING.md for the process of adding new gates.


Further Reading

๐Ÿ“– A Hand for Daenerys: Why Tyrion Is Missing from Your Vibe-Coding Council โ€” the article that started this project.


License

Slop-Mop Attribution License v1.0 โ€” free to use, modify, and redistribute with attribution.

P.S. Other than this line in the readme and a few scattered lines here and there, nothing in this project was written by a human. It is, for better or worse, the result of living under the slop-mop regime.

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

slopmop-0.5.0.tar.gz (163.9 kB view details)

Uploaded Source

Built Distribution

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

slopmop-0.5.0-py3-none-any.whl (197.3 kB view details)

Uploaded Python 3

File details

Details for the file slopmop-0.5.0.tar.gz.

File metadata

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

File hashes

Hashes for slopmop-0.5.0.tar.gz
Algorithm Hash digest
SHA256 ec7e13b576761bb8a9e0873fbf118757a6e83ff5a512ae3edbf7ea82e3640a3b
MD5 536ef284192526b012952d4f605a7416
BLAKE2b-256 c991a64860717c26c34e67178b479bcc40e71de28497886dddde257dfbfc83ba

See more details on using hashes here.

Provenance

The following attestation bundles were made for slopmop-0.5.0.tar.gz:

Publisher: release.yml on ScienceIsNeato/slop-mop

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

File details

Details for the file slopmop-0.5.0-py3-none-any.whl.

File metadata

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

File hashes

Hashes for slopmop-0.5.0-py3-none-any.whl
Algorithm Hash digest
SHA256 150b1cfe1ab3849fe7d51fc82ae3d96e28be4443f450121f951ca466b2c57bc2
MD5 5ecc310138664378443b03d2ae45a37d
BLAKE2b-256 875c6ccaaa67d8165625be2f76b7136589ad118945d7c37361f2c8effdbbfa75

See more details on using hashes here.

Provenance

The following attestation bundles were made for slopmop-0.5.0-py3-none-any.whl:

Publisher: release.yml on ScienceIsNeato/slop-mop

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