Quality gates for AI-assisted codebases โ catch the slop LLMs leave behind.
Project description
๐ชฃ Slop-Mop
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
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 scannedexclude_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:
<project_root>/venv/bin/<tool>or.venv/bin/<tool>โ project-local venv (highest priority)$VIRTUAL_ENV/bin/<tool>โ currently activated venv- 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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ec7e13b576761bb8a9e0873fbf118757a6e83ff5a512ae3edbf7ea82e3640a3b
|
|
| MD5 |
536ef284192526b012952d4f605a7416
|
|
| BLAKE2b-256 |
c991a64860717c26c34e67178b479bcc40e71de28497886dddde257dfbfc83ba
|
Provenance
The following attestation bundles were made for slopmop-0.5.0.tar.gz:
Publisher:
release.yml on ScienceIsNeato/slop-mop
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
slopmop-0.5.0.tar.gz -
Subject digest:
ec7e13b576761bb8a9e0873fbf118757a6e83ff5a512ae3edbf7ea82e3640a3b - Sigstore transparency entry: 1023332569
- Sigstore integration time:
-
Permalink:
ScienceIsNeato/slop-mop@c3fd5f53d7cce40d20a3698299b8a97a510f025a -
Branch / Tag:
refs/tags/v0.5.0 - Owner: https://github.com/ScienceIsNeato
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@c3fd5f53d7cce40d20a3698299b8a97a510f025a -
Trigger Event:
push
-
Statement type:
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
150b1cfe1ab3849fe7d51fc82ae3d96e28be4443f450121f951ca466b2c57bc2
|
|
| MD5 |
5ecc310138664378443b03d2ae45a37d
|
|
| BLAKE2b-256 |
875c6ccaaa67d8165625be2f76b7136589ad118945d7c37361f2c8effdbbfa75
|
Provenance
The following attestation bundles were made for slopmop-0.5.0-py3-none-any.whl:
Publisher:
release.yml on ScienceIsNeato/slop-mop
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
slopmop-0.5.0-py3-none-any.whl -
Subject digest:
150b1cfe1ab3849fe7d51fc82ae3d96e28be4443f450121f951ca466b2c57bc2 - Sigstore transparency entry: 1023332632
- Sigstore integration time:
-
Permalink:
ScienceIsNeato/slop-mop@c3fd5f53d7cce40d20a3698299b8a97a510f025a -
Branch / Tag:
refs/tags/v0.5.0 - Owner: https://github.com/ScienceIsNeato
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@c3fd5f53d7cce40d20a3698299b8a97a510f025a -
Trigger Event:
push
-
Statement type: