laravel-inspector maintained by synerdy
Laravel Inspector
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.