# Sloppy

> Laravel-aware static analysis for the debt AI coding agents leave behind. 24 rules, git-diff review, a Rector and Pint fix pass, Pest expectations, CI annotations, agent rulesets and an MCP server. Deterministic, local, no LLM.

- **Type:** MCP server
- **Install:** `agentstack add mcp-heyosseus-sloppy`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [Heyosseus](https://agentstack.voostack.com/s/heyosseus)
- **Installs:** 0
- **Category:** [Developer Tools](https://agentstack.voostack.com/c/developer-tools)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [Heyosseus](https://github.com/Heyosseus)
- **Source:** https://github.com/Heyosseus/sloppy

## Install

```sh
agentstack add mcp-heyosseus-sloppy
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

Static analysis for the code-quality patterns AI coding agents leave behind.
  Runs on any PHP project — 25 rules, plus a Laravel set that knows Eloquent, controllers and queues.
  Hands the mechanical fixes to Rector and Pint, fails your Pest suite on new debt, annotates the
  pull request, and teaches the agent through CLAUDE.md and MCP.
  Deterministic and local — no model, no API key, no network.

  
  
  
  
  

  

Sloppy reads your PHP with a real parser and reports the shapes that turn into
maintenance cost: god methods, swallowed exceptions, likely N+1 queries,
business logic in controllers, abstractions that never earned their keep.

It ships 25 rules, a git-diff review mode and a baseline for existing
projects; a CI command that reads your pipeline instead of asking you to
describe it, with an official GitHub Action and a GitLab template; an
automated fix pass that hands the mechanical findings to Rector and the
formatting to Pint; a Pest plugin, so new debt fails in the same red-green
loop as everything else; and, for the agents writing the code, a generated
ruleset for `CLAUDE.md` and its equivalents plus an MCP server they can check
their own work against.

Every screenshot on this page is real output from the command in its caption.

## It is not an AI detector

Nobody can reliably prove authorship from source code, and a tool that claimed
to would be selling you a coin flip with a progress bar. Sloppy never guesses
who or what wrote a line. It detects **slop** — patterns that correlate with
fast, unreviewed output and with technical debt generally. A 200-line
controller action that writes to four tables, calls a payment API and swallows
a `Throwable` is a problem whether a person, an agent or a pair of them wrote
it at 3am.

So every number is about the *code*, never about its author:

| Sloppy says | It means | It does **not** mean |
| --- | --- | --- |
| `Confidence: 88%` | How sure the analyser is that the pattern it describes is really present | Any probability that the code was AI-generated |
| `Score: 67/100` | A code-quality risk measure for the analysed paths | "67% of this code is AI-generated" |
| `SL107 Swallowed Exception` | This catch block does nothing observable with the failure | An accusation about who wrote it |

Sloppy is deterministic and local. Your source never leaves the machine, and
the same code always produces the same report — which is what makes it usable
as a gate.

## Install

```bash
composer require --dev heyosseus/sloppy

php artisan sloppy          # Laravel
vendor/bin/sloppy           # any PHP project
```

The service provider is discovered automatically. Publish the config when you
want to tune it:

```bash
php artisan vendor:publish --tag=sloppy-config
```

### Or without adding a dependency

Deciding whether a tool is worth a `composer.json` entry is easier once you
have seen what it says about your code. Two ways to run it against a project
that has never heard of it:

```bash
# Once per machine, for every project on it
composer global require heyosseus/sloppy
sloppy                                    # run it from anywhere inside a project

# Or a single file, no Composer resolution at all
curl -L -o sloppy.phar https://github.com/heyosseus/sloppy/releases/latest/download/sloppy.phar
chmod +x sloppy.phar
./sloppy.phar
```

Both work with no configuration file. Sloppy finds the project by walking up to
the nearest `composer.json`, and analyses the PSR-4 source roots declared there
when there is no `config/sloppy.php` to say otherwise — so `src/` on a
framework-free project and `app/` on a Laravel one are both found on their own.
Pass `--project` when the directory it picked is not the one you meant.

Everything works this way except the `php artisan sloppy:*` commands
themselves, which need the package installed in the project. `sloppy fix` still
drives Rector and Pint, because it resolves them from the *analysed* project's
`vendor/bin` rather than its own.

Each release attaches `sloppy.phar.sha256` alongside the binary, if you want to
check it:

```bash
curl -L -o sloppy.phar.sha256 https://github.com/heyosseus/sloppy/releases/latest/download/sloppy.phar.sha256
sha256sum -c sloppy.phar.sha256
```

Requirements: **PHP 8.3+**. Laravel 12 or 13 for the Artisan commands and the
`SL2xx` rules; everything else runs anywhere.

## Which command do I want?

```bash
php artisan sloppy:help      # or: vendor/bin/sloppy guide
```

Eleven commands, and the moment each one belongs to. Both names run the same
code, so use whichever your project has.

| Every day | | |
|---|---|---|
| `sloppy` | `sloppy scan` | Analyse the project and rank what is worth reading first |
| `sloppy:diff` | `sloppy diff` | Report what a change introduced, against a git revision |
| `sloppy:review` | `sloppy review` | The same change, ordered by risk rather than by file |
| `sloppy:baseline` | `sloppy baseline` | Accept what is already there, so only new findings fail |
| `sloppy:watch` | `sloppy watch` | Keep the score on screen, redrawing as files change |
| `sloppy:help` | `sloppy guide` | This list |
| **In a pipeline** | | |
| `sloppy:ci` | `sloppy ci` | Analyse a change the way the surrounding CI system reports it |
| `sloppy:fix` | `sloppy fix` | Hand the fixable findings to Rector, then format with Pint |
| `sloppy:health` | `sloppy health` | The score and what is dragging it down, from a cached snapshot |
| **For agents** | | |
| `sloppy:rules` | `sloppy rules` | Write this project's rules into `CLAUDE.md`, `AGENTS.md` and friends |
| `sloppy:mcp` | `sloppy mcp` | Serve scan, diff, rules and health over MCP |

For one command's options, `sloppy help ` or
`php artisan sloppy: --help`.

## Contents

- [Which command do I want?](#which-command-do-i-want) · [Scan a project](#scan-a-project) · [Anatomy of a finding](#anatomy-of-a-finding)
- [Review a change](#review-a-change) · [Adopt on an existing codebase](#adopt-on-an-existing-codebase)
- [Does it just complain about everything?](#does-it-just-complain-about-everything)
- [Slop score](#slop-score) · [Severity and confidence](#severity-and-confidence)
- [Rules](#rules) · [Configuration](#configuration) · [Custom rules](#custom-rules)
- [What to read first](#what-to-read-first) · [Risk](#risk)
- [JSON output](#json-output) · [Editors and code scanning](#editors-and-code-scanning)
- [Exit codes](#exit-codes) · [CI](#ci) · [GitHub Action](#github-action) · [GitLab CI](#gitlab-ci)
- [Fix what can be fixed](#fix-what-can-be-fixed) · [What it can fix](#what-it-can-fix-and-what-it-will-not-pretend-to) · [In your test suite](#in-your-test-suite)
- [Watch while you work](#watch-while-you-work) · [Filament and NativePHP](#filament-and-nativephp) · [Rules for coding agents](#rules-for-coding-agents) · [Custom rules for agents](#your-custom-rules-teach-the-agents-too) · [MCP server](#mcp-server)
- [What Sloppy is not](#what-sloppy-is-not) · [False positives](#false-positives)

## Scan a project

```bash
php artisan sloppy                  # the configured paths
php artisan sloppy --path=app/Services
php artisan sloppy --rule=SL101 --rule=SL107
php artisan sloppy --min-confidence=80
php artisan sloppy --explain        # include each rule's "why this matters"
```

The screenshot at the top of this page is that command run against a small
application written badly on purpose. It ships as `tests/Fixtures/Sloppy`, so
you can reproduce it.

### Anatomy of a finding

One file, seven findings, the whole report:

Every finding carries the same five things: **where** it is, **what** was
measured, **how sure** the analyser is, **why** the pattern is often a problem,
and **what to do** about it. The last one is the point — a finding you cannot
act on is noise with a line number.

## Review a change

The most useful command if you work with coding agents:

```bash
php artisan sloppy:diff             # working tree vs HEAD
php artisan sloppy:diff HEAD~1      # vs the previous commit
php artisan sloppy:diff main        # everything this branch changed
```

It separates what your change *introduced* from what it merely *inherited*, and
only new findings can fail the build.

Three things worth knowing about how it works:

- **It reviews the working tree, not just commits.** Uncommitted and untracked
  files are included, so an agent's new class is reviewed before it is
  committed rather than after.
- **Findings are matched by fingerprint, not by line number.** Adding an import
  at the top of a file does not turn every existing finding in it into a new
  one.
- **Cross-file rules still see the whole project.** Rules only *run* on changed
  files, but the project index is built from everything, so `SL303` can still
  tell that an interface has exactly one implementation in a file the diff
  never touched.

## Adopt on an existing codebase

Turning Sloppy on for the first time should not mean fixing everything first.
Record what is there today, commit the file, and gate on what comes next:

```bash
php artisan sloppy:baseline
```

Baseline entries are keyed on rule, file and a rule-supplied fingerprint —
usually a class and member name — and deliberately **not** on line numbers, so
a baseline survives ordinary editing. If a finding occurs more often than the
baseline recorded, the extra occurrences are new.

`--force` replaces an existing baseline; `--rule=` baselines only some rules.

## Does it just complain about everything?

That is the failure mode of every analyser, so Sloppy runs its whole rule set
over its own source in CI:

One finding, and it is a real one: `NodeHelper` is a large class. Splitting it
into five so that rules import three of them to ask three questions would be
exactly the ceremony this package exists to discourage, so the decision is
recorded in `tests/Feature/SelfCheckTest.php` with its reasoning — and that
test fails on any *new* finding about Sloppy's own code. Four others it found
were genuine, and they were fixed.

The other half of the answer is `tests/Fixtures/Good`: deliberately ordinary
Laravel code, with a test asserting that all 25 rules report **zero** findings
on it at a score of 100.

## What to read first

`sloppy:diff` answers *did this change make it worse?* `sloppy:review` answers
the question you have immediately afterwards: **of everything this change
touched, what deserves reading?**

```bash
vendor/bin/sloppy review origin/main
php artisan sloppy:review origin/main
```

```
  Sloppy review

  161 file(s) changed, 12,198 lines  ·  score 5 → 12 (+7)

  Read in this order
    1. app/Actions/Product/CloneProductToTenantAction.php   risk 70.5
         new SL111 Copy-Paste Drift  (76%, risk 9.9, in hunk)
         new SL102 God Class  (73%, risk 9.5, in hunk)
         new SL111 Copy-Paste Drift  (68%, risk 8.8, in hunk)
         new SL204 Query Inside Loop  (84%, risk 4.4, in hunk)
         and 9 more in this file, 2 x SL104, 7 x SL204
    2. app/Filament/Imports/B2bCatalogImporter.php   risk 48.0
         new SL102 God Class  (91%, risk 11.8, in hunk)
         new SL101 God Method  (76%, risk 9.9, in hunk)

  Skim
    4 file(s), risk under 5
      app/Support/TenantFrontend.php  risk 3.4

  No attention needed
    132 file(s) changed with no findings

  Resolved by this change
    3 finding(s) no longer reported
      2 x SL101 God Method
      1 x SL204 Query Inside Loop
```

Three tiers, so you know where to stop. Same analysis, same score and the same
exit code as `sloppy:diff` — only the presentation differs, so adopting the
reading order changes no build outcome.

**`in hunk` is the part no other tool can do.** A god method your change
*created* and a god method it merely stood next to are not the same finding.
Telling them apart needs the diff's hunks at rule time, and nothing else in
this space has them.

## Risk

Risk and the slop score answer different questions, and conflating them leads a
team to chase the wrong one.

| | Slop score | Risk |
| --- | --- | --- |
| Question | How is this codebase? | What should I read next? |
| Normalised | Yes, by size | No, absolute |
| Aware of your change | No | Yes |
| Moves a baseline | Yes | Never |

```
risk = severity_weight × (confidence / 100) × novelty × proximity × reach × exposure

reach     = 1 + log10(1 + blast_radius) × reach_weight
novelty   = new 1.0 | inherited 0.25
proximity = inside a changed hunk 1.0 | elsewhere in a touched file 0.3
exposure  = 1 + (1 − coverage) × exposure_weight | 1.0 when no coverage report
```

Every factor defaults to 1.0 when it cannot be measured, which is what makes
the model safe to extend: a project with no coverage report ranks exactly as it
did before `exposure` existed.

**Reach is logarithmic on purpose.** A class with two hundred callers is not
two hundred times more urgent than one with a single caller; the tenth caller
costs less new attention than the first. One usage yields 1.30, ten yields
2.04, a hundred yields 3.00 — a 2.3× spread across two orders of magnitude,
which is roughly the spread a reviewer actually feels.

**A file's risk is the raw sum of its findings, not their density.** A file
with twelve findings should be read before a file with one, even if it is
longer. Density is the right measure for quality and the score already provides
it; total is the right measure for attention.

Every weight lives under `sloppy.risk` and every one is documented in
`config/sloppy.php`. Set `reach_weight` to `0.0` to rank on severity and
confidence alone.

### Show me the arithmetic

Add `--explain-risk` to any command and every derived number prints its own
working:

```bash
vendor/bin/sloppy scan --explain-risk
```

```
    108  HIGH     SL102  God Class  (91% confidence)
         NodeHelper spans 1021 lines with 59 methods…
         → Group the members that change together…
         risk  10.0 (high) × 0.91 (confidence) × 1.00 (novelty unknown) × 1.00 (whole file) × 2.45 (27 usages) = 22.27
```

A tool that weights findings owes you its weights. A number you can watch it
derive is not a magic number.

## Slop score

A single deterministic number for the analysed paths:

  

```text
penalty  = Σ  weight(severity) × confidence / 100
units    = max(1, analysedLines / lines_per_unit)
density  = penalty / units
coverage = min(1, Σ affectedLines / analysedLines)
score    = 100 − min(100, density × penalty_multiplier × (1 + coverage))
```

- Step 1 makes a finding we are half sure about cost half as much.
- Step 2 normalises by codebase size, so a large application is not punished
  merely for being large.
- Step 4 doubles the penalty when findings blanket the codebase and barely
  moves it when they are localised.

Default weights are `critical: 20`, `high: 10`, `medium: 4`, `low: 1.5`,
`info: 0.5`. Weights, the multiplier, `lines_per_unit` and the band thresholds
are all configurable under `sloppy.score`. Same input, same score — always.

Because the score is a density, a small codebase full of findings bottoms out
quickly: the 472-line demo application in the first screenshot scores 0. That
is the arithmetic working, not a verdict on 472 lines of anything.

## Severity and confidence

They answer different questions, and both appear on every finding.

**Severity** — how much this matters if it is real. `critical`, `high`,
`medium`, `low`, `info`. Set by the rule, overridable per rule in config. This
is what `fail_on` compares against.

**Confidence** — how sure the analyser is that the pattern it describes is
actually present, 0–100. A 300-line method is a measurement, so confidence is
high. A possible N+1 depends on eager loading the analyser may not be able to
see, so confidence is lower. Abstraction inflation is a heuristic, so it never
reports above 78.

No rule ever reports 100% confidence. These are heuristics, and a heuristic
that claims certainty is lying.

## Rules

25 rules ship. Every one has tests proving both that it fires on the pattern
and that it stays quiet on ordinary Laravel code.

### PHP

…

## Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [Heyosseus](https://github.com/Heyosseus)
- **Source:** [Heyosseus/sloppy](https://github.com/Heyosseus/sloppy)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** yes
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/mcp-heyosseus-sloppy
- Seller: https://agentstack.voostack.com/s/heyosseus
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
