Repository navigation
Releases: Heyosseus/sloppy
Release list
v1.7.2
A patch release that makes some output formats cheaper, and the test suite much faster.
Fixed
--format=rector,sarif,githubandgitlabno longer read the git history.
Every format used to compute the risk ranking first, which runsgit logand looks
for a coverage report, although only the console, JSON and Markdown reports use it.
A CI run that only annotates or writes SARIF now starts fewer git processes.
Internal
- The test suite runs in parallel.
composer test:fastruns the 1,400 tests that stay
inside PHP in seconds; the ones that build git repositories, start processes or boot
Laravel are in theintegrationgroup, and CI runs both.
Full changelog: v1.7.1...v1.7.2
v1.7.1
Fixed
sloppy --versionreports the right version. Thev1.7.0tag was
placed one commit before the release commit, so installs of 1.7.0 still
called themselves 1.6.0. Upgrading to 1.7.1 fixes it.agents install --dry-runnames the files a real install writes.
In projects that use Laravel Boost, the dry run listedCLAUDE.md,
GEMINI.mdandAGENTS.md, but the install writes the Boost guideline
(.ai/guidelines/sloppy.blade.php) instead.
Upgrading
composer update heyosseus/sloppy. The GitHub Action and the CI templates
install ^1.7, so they pick up 1.7.1 on their next run without changes.
Full changelog: v1.7.0...v1.7.1
v1.7.0
Sloppy 1.7.0
Sloppy now checks agents beyond Claude Code. Gemini CLI and Codex get the same two hooks: findings an edit introduced are handed back right after the edit, and the agent is sent back once before it finishes.
Added
- Hooks for Gemini CLI and Codex.
sloppy agents install --host=gemini --host=codex(repeatable;php artisan sloppy:agentstakes it too) writes.gemini/settings.jsonand.codex/hooks.json, plus each agent's ruleset (GEMINI.md,AGENTS.md). Where Laravel Boost is installed, every agent gets the Boost guideline instead. - A Codex patch that touches several files has each file's new findings reported.
- Gemini CLI gets findings as context on the tool's result, so the model still knows its write went through.
sloppy rules --format=geminiwritesGEMINI.md.agents uninstall --hostlimits removal to the agents named. Without it,uninstallalso cleans Sloppy out of.gemini/settings.json(hooks and MCP server),.codex/hooks.json,GEMINI.mdandAGENTS.md.
Upgrading
- Nothing to do. Without
--host,installwires up Claude Code only, as before, and hooks written by 1.6 keep working unchanged. - Codex: after installing, run
/hooksin Codex and trust the two Sloppy hooks. Start Codex from the project root, because its hooks runphp vendor/bin/sloppyfrom the session's directory. - No
--localfor Gemini CLI and Codex. They have no personal settings file inside the project, so--local, or an install with novendor/bin/sloppy, skips them with a warning.
See Coding agents for details.
v1.6.0
Sloppy 1.6.0
A correctness release. Fewer false positives, git handling that survives monorepos and unusual file names, a safer MCP server and sloppy fix, and a diff score that describes the whole tree.
⚠️ Before you upgrade
- Configuration is now validated. A value of the wrong type or out of range stops the run with exit code 2 and lists every problem. An unknowkey only prints a warning, with a "did you mean" hint.
- The standalone binary merges your config over the defaults, as Artisan always has. A
sloppy.phpthat left outfail_onorexcludenow the defaults for them. - More things exit with code 2: a
--paththat doesn't exist, an unknown option, a number option that isn't a whole number, and a file that doesn't parse inci. Use--allow-parse-errorsto keep tarchitectureno longer accept options they used to ignore. - SL501 fingerprints no longer contain a line number, so baselined SL501 entries reappear once. Run
sloppy baselineagain. - More code is analysed: top-level functions, closures,hat extend your own base model. A full
scanmay reportfindings it missed before;diffandcistill report only new ones. sloppy fixruns Pint only on a Laravel project or one wn the files it changed.agents install --localwritesCLAUDE.local.mdinstead of editingCLAUDE.md.- The CI templates and the GitHub Action now install
^1.6
✨ New
sloppy agents uninstallremoves everything `agents r other settings exactly as they were.diff --merge-basecompares against where your branch forked. It's the default inci.diffaccepts every--format, anddiffandciSL303.layer_suffixes, sharing one list with SL301 in the shipped config.- CI now covers Windows and PHP 8.5, plus a
--no-deve test.
🐛 Fixed
Rules
- SL204/SL210 no longer count the collection a loop iterates over as inside the loop.
- Carbon and other static calls are no longer taken for que
- SL203 no longer reports after
load(), onwith('relation:columns'), or on timestamp and cast attributes. - SL108/SL110 no longer treat "not null" as "truthy". SL110ed by
foreachor destructuring. - SL105 accounts for dynamic calls, and SL303 counts enums and anonymous classes as implementations.
- A class named
Clientis no longer assumed to be an HTTP - SL501 catches three more suppression forms. SL103 no longer counts
else ifas nesting. - No more duplicate SL111 findings. SL109 handles trailing methods, and SL112's stub patterns are tightened.
- No crash on first-class callables (
foo(...)), and anonymous classes are reported once. - The blast-radius off-by-one is fixed, and quadratic slowdmoved.
Git and diff
- Works for a project in a subdirectory of its repository, and with quoted or non-ASCII file names.
- Ignores your own diff settings: colour, external diff too
- Handles files moved with plain
mv, deleted files, and repositories with no commits yet. - The diff score now describes the whole tree instead of al
- Claude Code hooks skip the score entirely, so they stay fast.
Output
- Invalid UTF-8 no longer breaks JSON, SARIF or GitLab repo
- Annotation and SARIF paths are relative to the repository root.
errorsis always a JSON object.
MCP server
- Stray output and PHP warnings stay off stdout.
pinganswers{}, and the protocol version is negotiat- Batched and malformed requests are handled, tool arguments are validated, and a relative project path is resolved against the server's root.
Everything else
sloppy fixnever overwrites a Rector config it didn't w--no-rector`, and exits 0 on a dry run with pending changes.- Shared agent hooks use
${CLAUDE_PROJECT_DIR}instead of a path from one machine, and settings files keep their formatting. watchrestores your terminal on Ctrl+C and polls less o.- The GitHub Action no longer pastes its inputs into its shell script.
- The phar now includes
resources/.
Full changelog: https://github.com/heyosseus/sloppy/com
v1.5.0
Sloppy 1.5.0: your architecture, taught to the agents writing your code
1.4.0 let a project describe its architecture. 1.5.0 makes sure the agents writing the code learn it in two places: before they write a file, and on the edit that breaks it. It also makes the architecture easier to write down: init drafts it from your code, import translates deptrac, and graph draws it.
For agents
-
Architecture in every ruleset. If your project declares an architecture,
sloppy rulesnow adds an "Architecture of this project" section toCLAUDE.md,AGENTS.md, Cursor and the Boost guideline. It lists each role, where its classes live, what their names end in, and the policy that applies, written as instructions. Projects that declare nothing get the same file as before. -
sloppy architecture placesays where a new class belongs: the role, namespace, file, naming, and what it may depend on and do.vendor/bin/sloppy architecture place "an action that refunds an order" --name=RefundOrder -
New MCP tools:
sloppy_architecture: the whole architecture, one class explained, or the graph.sloppy_place: where a new class belongs.sloppy_architecture_prompt: what an agent needs to write a profile.
The server now tells agents to call
sloppy_placebefore creating a class.
New checks
| Rule | Reports |
|---|---|
| SL307 Misplaced Class | A class the change adds in a path listed under covers that matches no role. Runs in sloppy diff and the agent hooks only, so existing classes are never reported. |
| SL308 Role Shape | A public method the role doesn't allow, or a class that isn't final when its role must be (public_methods, final). The laravel-actions preset now limits actions to handle() and the lorisleiva/laravel-actions hooks. |
'architecture' => [
'preset' => 'laravel-actions',
'covers' => ['app/*'],
'policies' => [
'controller' => ['public_methods' => ['__invoke'], 'final' => true],
],
],Writing it down
sloppy architecture initdrafts a profile from your code:- It picks the closest preset.
- It adds a role for each family of classes the preset doesn't cover (
*Observer,*Presenter, …). - It asks about any namespace it can't place instead of guessing.
sloppy architecture importtranslatesdeptrac.yaml: layers become roles, collectors become matchers, and the ruleset becomes allow lists. Anything that can't be translated is named in the file's comments.sloppy architecture promptgives an agent the format, what the code shows and a draft, so it can turn yourARCHITECTURE.mdinto a profile. Sloppy itself never calls a model.sloppy architecture graphdraws which roles depend on which, as Mermaid, Graphviz or JSON. Forbidden edges are shown in red.
init and import write sloppy-architecture.php in the project root, which Sloppy reads instead of sloppy.architecture. Nothing is written unless you confirm at the terminal or pass --write.
Changed
symfony/yamlis now a runtime dependency, used to read deptrac configuration.- Evidence sources now receive the project index, so diff-only checks recognise classes exactly the way rules do.
Upgrading
Nothing changes unless you opt in. Projects without a declared architecture get the same rules, findings and rulesets as in 1.4.0.
📖 Describing your architecture · Coding agents · Full changelog
v1.4.0
Sloppy 1.4.0: describe your architecture, and Sloppy enforces it
Sloppy no longer has to guess what your classes are from their names. You tell it your project's layers, what each one may do, and where its module boundaries are. Three new rules then report any code that breaks them.
Roles
The new architecture setting gives each class a role: couest, middleware, service, or one of your own. A rolecan match a class by namespace, path, suffix, parent, ancestors, interfaces, traits, attributes or kind. SL201, SL202, SL206, SL207 and SL208 now
read these roles. So if your controllers live in `App\Ui\Htayer, you say so once and every rule uses it.
'architecture' => [
'preset' => 'laravel',
'policies' => [
'controller' => ['may_not' => ['db'], 'may_not_depe
],
'boundaries' => [
'modules' => 'App\Modules\{module}\*',
'public' => ['Contracts\*'],
],
],New rules (opt-in)
| Rule | Reports |
|---|---|
| SL304 Layer Violation | A dependency a role may not have, named by role or by a glob such as Illuminate\* |
| SL305 Forbidden Capability | A DB query or write, a d read, a view, or a container lookup in a role whose policyforbids it. It only reports what it can prove, so $order->save() on a variable is never a finding. |
| SL306 Boundary Violation | A module that uses anotherf its public surface or the shared kernel |
SL208 now also reports outbound HTTP in any role whose poli
These rules run only when your configuration declares a polheck. A project that declares nothing gets the same 26rules and agent rulesets as before.
Presets
The presets are laravel (the default, which works exactly as before), laravel-actions, service-repository, ddd, hexagonal and modular.
Each preset ships roles, policies or boundaries, and you ca a time.
Intended abstractions
Setting 'intended_abstraction' => true on a role tells SL and wrappers in that role are deliberate. A port with asingle adapter is then no longer reported as needless indirection.
sloppy architecture
sloppy architecturecounts the classes in each role and lists every policy and boundary in force, with where each one came from. -sloppy architecture OrderControllerexplains one class:hich roles it lost, and which policy it is held to.- The command can write console output or JSON. Laravel projects can also run it as
php artisan sloppy:architecture. - Custom rules can check a class's role with$context->rolasRole($class, 'controller').
### Changed
- Each class now has exactly one role. Where the old chiable signal wins. A FormRequest kept underHttp\Controllersis a form request. An Eloquent model whose name ends inAction, or that sits inApp\Domain, is a model, not a service7,485 declarations in eight real applications, this changed findings.
### Upgrading
You don't need to change anything. With no new configuratios in 1.3.1. To try the new rules, add apoliciesorboundariesentry, or pick a preset. Then runsloppy architectureto see what is in force.
📖 Describing your architecture · [Full changelog](https://github.com/heyosseus/sloppy/compare/v1.3
v1.3.1
fix: lower SL107 when a comment explains the catch
v1.3.0
Merge pull request #30 from Heyosseus/release/1.3.0 feat: add the stable API for phpstan-sloppy
v1.2.0
Triaged scan output
Once a run has more than 20 findings, sloppy groups the report into four sections:
- Fix first: defects such as swallowed exceptions, queries in loops and unfinished method bodies. They're ranked by risk and grouped by file and rule.
- Hotspots: maintainability findings, counted per file.
- By rule: a count for every rule, so nothing is hidden.
- Next: the commands that shorten the list without read
Use --all for the full list, --top=N to show more defece rule.
Clears the obvious findings for you
- sloppy fix now deletes comments that only restate theomments to keep them.
- A long first scan offers to baseline your existing findings, so later scans show only new ones. It only asks when you run it yourself at a terminal, never in CI.
Ranks by what changes
Findings in files that change often now rank higher. Slts, so the same checkout ranks the same way on any day.This affects the ranking, never the score.
Calibrated on real code
Tested against eight open-source Laravel apps, 782,578 lines in total:
- Findings dropped from 5,097 to 4,075.
- Scores now range from 25 to 90. Before, they ran from 0 to 87, and mature apps like Snipe-IT and Pixelfed scored 0.
What changed:
- SL101: "many calls" and "many collaborators" now counrately, they were the only trigger for 41% of itsfindings.
- Score: no single rule can take more than 30 points ofapping findings no longer count the same lines twice.
- SL109: no longer flags comments at the end of a block, or single lines of a multi-line comment.
Agent guardrails
- SL112 Placeholder Implementation catches method bodies left unwritten, such as // ... existing code ... or a "not implemented" throw.
- SL503 Weakened Test catches tests made to pass by cheth assertions removed, or given assertTrue(true).
- Claude Code hooks now work on Windows without Git Bash. Re-run sloppy agents install to update your settings.
Upgrade notes
- Different scores: expect different numbers from 1.1.x, usually higher on large codebases. The score stored in an existing baseline was calculated with the old formula, so it won't match ne
- JSON output: each finding gains a tier field.
- New config keys: score.rule_cap, risk.churn_weight, rrule tier option.
The full list of changes is in CHANGELOG.md.
v1.1.1 — Windows hook fix, new docs, new look
A patch release: one bug fix for the Claude Code hooks, a cleaner feedback message, and a new home for the documentation.
Fixed
sloppy hookno longer misses a payload redirected from a file on Windows. Symfony Console measures the terminal before any command runs. On Windows, itsmode CONprobe inherited standard input and swallowed a redirected file, so the edit went unchecked. Piped input, which is what Claude Code sends, was not affected. The binary now sets a terminal width for hook runs, which skips the probe.
Changed
- The hook feedback no longer tells the agent to explain a deliberate finding "rather than in a code comment". That contradicted SL107's own advice to leave a comment when a swallowed failure is intentional.
Documentation
- The README is now a landing page. It is 208 lines, down from 1,417, and opens with real output of the Claude Code hook.
- The reference material moved to
docs/word for word: