heyosseus/sloppy
Sloppy is a Laravel package by heyosseus that trims boilerplate and speeds up everyday development tasks with a simple, forgiving API. Drop it into any Laravel app to write less code and ship features faster.
Sloppy is a dev-only Composer package. Nothing ships to production, nothing leaves the machine, and the whole thing removes cleanly. Install it into require-dev:
composer require --dev heyosseus/sloppy
# If you already run PHPStan, this is what surfaces the rules as PHPStan errors
composer require --dev heyosseus/phpstan-sloppy
Before you write a line of config, read three things in this order:
vendor/bin/sloppy rules — the authoritative list of the 34 rules. Read it before configuring, not after you have hit findings you don't understand.config/sloppy.php — exclude, fail_on, architecture, and the SL301/SL303 layer-suffix lists are the only knobs most teams ever touch.vendor/bin/sloppy scan --path=app — a one-pass read of your current debt, so you know what your baseline looks like before you gate anything on it.Your first real use case should be diff-scoped, not full-tree. On a mature Laravel app a full scan produces a wall of pre-existing findings, and none of it is actionable. The value is in:
vendor/bin/sloppy diff --merge-base
--merge-base compares against the point where your branch forked, and it is already the default in sloppy ci. That is the command you wire into a pre-commit hook or PR job on day one. Once diff mode is quiet, move on to the part that actually changes your day — the agent hooks:
vendor/bin/sloppy agents install # Claude Code
vendor/bin/sloppy agents install --host=gemini --host=codex # Gemini CLI + Codex
That writes per-host hook config (.claude/settings.json, .gemini/settings.json, .codex/hooks.json) plus a matching ruleset (CLAUDE.md, GEMINI.md, AGENTS.md). Where Laravel Boost is installed, the Boost guideline is emitted instead.
One caveat worth resolving before you pitch this internally: the package metadata description describes an unrelated Eloquent seeder/faker tool. The release notes, CLI surface, and feature list all describe static analysis and agent guardrails. Trust composer show heyosseus/sloppy, the repo README, and vendor/bin/sloppy --help — not the summary line.
The highest-leverage integration is the PHPStan extension, because the rules then arrive through the tool your team already runs, with the editor squiggles and baseline handling you already have:
# phpstan.neon
includes:
- vendor/heyosseus/phpstan-sloppy/extension.neon
Keep vendor/bin/sloppy for what PHPStan doesn't do: diff scoping, the fix pass, agent hooks, MCP, and the Pest expectations. Don't run two separate "lint" commands in CI — one gate, two front doors.
diff accepts every output format, so one mental model covers local reading and CI:
vendor/bin/sloppy diff --merge-base # read before you commit
vendor/bin/sloppy ci --format=github # PR annotations
vendor/bin/sloppy ci --format=sarif > sloppy.sarif # code scanning upload
vendor/bin/sloppy ci --format=gitlab
Annotation and SARIF paths are emitted relative to the repository root, which matters in a monorepo where the Laravel app lives in a subdirectory. That case works correctly as of 1.6.
Two mechanisms are easily confused:
sloppy baseline records existing findings (notably SL501 suppressions) so they stop being reported.Treat baseline regeneration as a reviewed change with a reason in the commit message, never a reflex to turn a red build green. If your CI shows baseline growth, that is the signal, not the noise.
vendor/bin/sloppy fix --dry-run # exits 0 even with pending changes
vendor/bin/sloppy fix # applies
vendor/bin/sloppy fix --no-rector # Pint only
This is the natural "done" step after an agent finishes a batch of edits. Two safety properties: fix will not overwrite a Rector config it did not write, and Pint only runs on a Laravel project or on the files it changed — so it stays fast and doesn't reformat the whole tree. A dry run exiting 0 with pending changes is deliberate; it's meant to be informational in CI.
Every supported host gets the same pair of hooks. Findings that an edit introduced are handed back immediately after the write, and the agent is sent back once more before it declares itself finished. The second hook is the important one — it catches the case where the model's closing summary papers over a violation it just introduced.
For Gemini CLI the findings arrive as context attached to the tool result, so the model still knows the write succeeded. For Codex, a multi-file patch has each file's new findings reported separately.
A rollout pattern that survives contact with a real team:
fail_on set to none — measurement only, no blocking.exclude and layer suffixes accordingly.fail_on.Rollback is scoped, not all-or-nothing:
vendor/bin/sloppy agents uninstall --host=gemini
Don't hand-maintain CLAUDE.md, GEMINI.md and AGENTS.md as three drifting documents:
vendor/bin/sloppy rules --format=gemini # writes GEMINI.md
Regenerate in a script alongside dependency updates. Verify one thing first: if Laravel Boost is installed, the Boost guideline wins, so the Sloppy ruleset file is present but not the effective instruction set. That precedence is usually what you want — just don't assume otherwise when debugging why an agent ignores a rule.
The MCP server exposes findings as tools, wired in automatically by agents install. Pair it with coverage-aware reading order, which hands the agent files ordered by test coverage, and you get an agent reading the relevant part of the codebase instead of grepping around. On a large app this is the difference between a plausible patch and a correct one.
If your team prefers "no new findings" as an invariant rather than a review comment, Pest expectations let you assert it inside the suite — so the local test run and the CI gate are the same command, and nobody can claim they didn't know.
Exit code 2 means "you told me something invalid," not "you have findings." A nonexistent --path, an unknown option, a non-integer numeric option, a config value of the wrong type or out of range, or an unparseable file under ci all exit 2. Config validation now reports every problem at once, which is genuinely helpful — but it also means a config that used to limp along with a silently-ignored option will now hard-stop the run. Unknown keys only warn, with a "did you mean" hint. Use --allow-parse-errors when a partially broken tree shouldn't fail the job.
Pin the version and read the upgrade notes. 1.6 and 1.7 both shipped behaviour changes, not just fixes. The ones that bite: the standalone binary now merges your sloppy.php over the defaults (a config that omitted fail_on or exclude now inherits them); SL501 fingerprints no longer contain a line number, so baselined entries reappear once and need a re-baseline; and more code is analysed than before — top-level functions, closures, classes extending your own base model — so a full scan may surface findings it previously missed (diff and ci still only report new ones).
Codex needs two manual steps after install. Run /hooks in Codex and trust the two Sloppy hooks, and start Codex from the project root — its hooks execute php vendor/bin/sloppy from the session's directory, so a subdirectory launch silently does nothing useful.
--local means different things per host. It writes CLAUDE.local.md rather than editing CLAUDE.md. Gemini CLI and Codex have no personal settings file inside the project, so --local (or an install with no vendor/bin/sloppy) skips them with a warning rather than failing.
Hooks skip the diff score entirely for speed, so a hook and a sloppy diff run will not always agree on the score. Trust the hook for "did this edit make things worse," trust ci for the gate.
The diff score describes the whole tree, not just the changed files. Useful for trend tracking, misleading if you read it as "the quality of this PR."
Deterministic and local, by design. No LLM in the loop, so results are reproducible and safe to run in CI and in an agent hook on every write. Resist the temptation to bolt an LLM summariser on top for "better messages" — you lose the reproducibility that makes the gate trustworthy.
watch is for interactive work only. It restores your terminal on Ctrl+C and polls conservatively, but it is not a substitute for the diff gate in CI.
Treat architecture and layer suffixes as code review, not config. SL301/SL303 boundaries and SL303.layer_suffixes (now shared with SL301) are the most opinionated part of the package. Get them reviewed by someone who owns the architecture, or you will be enforcing a boundary nobody agreed to — and agents will dutifully follow it.
How can I help you explore Laravel packages today?