synerdy / laravel-inspector
Tells you whether a Laravel app is still a clean install, and lists every file the developer added on top of it.
Requires
- php: ^8.2
- ext-json: *
- illuminate/console: ^10.0|^11.0|^12.0|^13.0
- illuminate/support: ^10.0|^11.0|^12.0|^13.0
Requires (Dev)
- orchestra/testbench: ^8.0|^9.0|^10.0|^11.0
- phpunit/phpunit: ^10.5|^11.0|^12.0
README
English · Polski
Answers one question: is this Laravel app still a clean install, or did I already build something in it?
You spin up an app in Herd, come back three months later, and cannot remember whether it was a scratch install or the one with the admin panel. Install this package, run one command, and get a list of every controller, model, view, migration, route and test that exists on top of the stock Laravel skeleton.
Dependencies are never scanned. vendor/ and node_modules/ are pruned before they are descended into, so the
command finishes in milliseconds even on a large app.
Installation
composer require --dev synerdy/laravel-inspector
The service provider is auto-discovered. Publishing the config is optional:
php artisan vendor:publish --tag=inspector-config
Usage
php artisan inspect
Laravel Inspector
Path C:/Users/marcin/Herd/modsxtest1
Laravel 13.0.0
Starter kit Livewire starter kit (livewire/flux ^2.13.1)
Scanned app, bootstrap, config, database, public, resources, routes, tests (absent: lang)
Files 95 scanned, 36 stock, 54 from starter kit, 5 added
NOT CLEAN 5 file(s) added.
Views (2)
resources/views/components/passkey-registration.blade.php 2026-08-24
resources/views/components/passkey-verify.blade.php 2026-08-24
Migrations (1)
database/migrations/2024_01_01_000000_create_passkeys_table.php 2026-08-24
Published config (1)
config/modsx.php 2026-08-27
Frontend sources (1)
resources/js/passkeys.js 2026-08-24
Livewire starter kit (livewire/flux ^2.13.1) (54 file(s), not listed -- use --starter-kit-files)
Not checked
Only file names are compared, not contents. Edits inside these stock
files are invisible to this report:
resources/views/welcome.blade.php
app/Models/User.php
app/Providers/AppServiceProvider.php
bootstrap/app.php
database/seeders/DatabaseSeeder.php
routes/web.php was compared and matches the starter kit version.
The three verdicts
| Verdict | Exit code | Meaning |
|---|---|---|
CLEAN INSTALL |
0 |
Bare laravel new. Nothing beyond the skeleton. |
CLEAN INSTALL + kit name |
0 |
Created with a starter kit, nothing built on top of it. |
NOT CLEAN |
1 |
Files were added, or a stock file was edited. |
The middle one is the case that used to be hard to see: an app made with laravel new --livewire ships around
60 files you never typed, and it is still an empty project.
Options
| Option | Meaning |
|---|---|
--json |
Emit the report as JSON instead of text |
--all |
List every file instead of the first few per category |
--limit=N |
Files listed per category (default 10) |
--starter-kit-files |
List the starter kit's files instead of just counting them |
Since the exit code is meaningful, this is scriptable — to find the untouched apps in a folder full of them:
for app in ~/Herd/*/; do [ -f "$app/artisan" ] || continue if php "$app/artisan" inspect >/dev/null 2>&1; then echo "clean: $app" else echo "in use: $app" fi done
How it decides
Files: by name
Detection is by file name, against a built-in list of the files that ship with a stock laravel/laravel
application. That list is a union across Laravel 9 through 13, so the package never asks which version you are
running and never needs the network.
A file whose path is not on that list was put there by somebody. That is the entire rule.
Scanned directories:
app bootstrap config database lang public resources routes tests
Never scanned: vendor, node_modules, storage, bootstrap/cache, public/build, public/storage,
public/hot, .git, editor folders, log files, SQLite files.
Published package files under resources/views/vendor/ and public/vendor/ are reported — you published
them on purpose, so they are part of what you did to the app.
Starter kits: named, counted, collapsed
An app created with Breeze, Jetstream or one of the Laravel 12+ starter kits ships dozens of files you did not
type. The kit is identified from composer.json, package.json and marker paths — those two manifests are read
only to name the kit and are never reported as application files — and its files are set aside, counted, and
shown as a single line instead of flooding the listing.
Attribution is deliberately conservative. Patterns cover subtrees a kit clearly owns (its auth pages, its layouts, its own tests) and leave ambiguous places alone. Showing a kit file as yours is a small annoyance; hiding a file you wrote would be a wrong answer, so the package errs toward showing too much.
routes/web.php: by content
routes/web.php is where the first route of a new project lands, so its content is compared — this is the
one stock file whose default is known well enough to check.
The comparison is structural, not byte-for-byte: the file goes through PHP's tokenizer and is stripped of
whitespace, comments and imports. Reformatting, CRLF line endings, an added declare(strict_types=1) and a
different Laravel version all pass as untouched, while a single added route does not. The known stock forms of
Laravel 9–13 and of the starter kits are recognised by name, so a kit's own web.php does not read as your edit.
What it does not do
Every other stock file is compared by name only. A rewritten resources/views/welcome.blade.php, a
relationship added to app/Models/User.php, middleware registered in bootstrap/app.php — none of these are
reported. The report names these files explicitly under "Not checked" rather than letting you assume they are
untouched.
Configuration
return [ // Directories walked. Everything else in the project root is out of scope. 'directories' => ['app', 'bootstrap', 'config', 'database', 'lang', 'public', 'resources', 'routes', 'tests'], // Merged on top of the built-in ignore list. A leading slash anchors a // pattern to the project root. 'ignore' => [ 'directories' => [], 'files' => [], ], // Treated as part of the baseline, on top of the stock Laravel skeleton. // Use this for anything else you did not write yourself. 'expected' => [], // true to detect and collapse starter kits, false to list their files as // yours, or an array to replace the built-in definitions. 'starter_kits' => true, // Replace the built-in grouping. First matching pattern wins. 'categories' => null, ];
Patterns accept * (one level), ** (any depth), a trailing / (directory and its contents), and a leading
/ (anchored to the project root). A pattern with no slash matches a basename at any depth.
Programmatic use
use Synerdy\LaravelInspector\Inspection\Inspector; use Synerdy\LaravelInspector\Inspection\Verdict; $report = app(Inspector::class)->inspect(base_path(), app()->version()); $report->verdict; // Verdict::Clean | CleanWithStarterKit | Modified $report->isClean(); // bool — false only for Verdict::Modified $report->addedCount(); // int $report->categories; // Category[] — key, label, files $report->starterKits; // StarterKit[] — label, package, version, files $report->starterKitNames(); // string[] $report->touchedStockFiles(); // StockFileStatus[] — stock files that were edited $report->toArray(); // the JSON payload
Testing
composer install vendor/bin/phpunit
License
MIT.