Vendor Cleanup Audit
Help the user inspect published vendor files (config/, database/migrations/, lang/vendor/, resources/views/vendor/) against the originals in vendor/. Identifies four categories: MODIFIED (drift), UNCHANGED (cruft), ORPHANED (leftovers from uninstalled packages), and MISSING (vendor files not published locally).
Backed by the leek/laravel-vendor-cleanup Composer package.
Treat command output as a lead, not a verdict
The vendor-cleanup:* commands are file finders, not auditors. Their categorization is heuristic (basename matching, content hashing with comments stripped, similar_text similarity). Always verify each finding yourself before recommending action to the user. Use the command output as a worklist of file paths to investigate, then independently confirm with Read, git log, composer show, and grep.
Mandatory verification per category:
- UNCHANGED — re-read both the local file and the vendor original. Confirm they are functionally equivalent. Watch for: locale-specific lang files where basename collides but content is intentional; configs where the published copy intentionally pins values that happen to match current vendor defaults but should not regress if vendor changes upstream.
- MODIFIED — read both files and produce a real diff (
diff -u vendor/.../file local/file). Decide if drift is intentional (custom values, env wiring) or accidental (stale copy from older package version, partial merge). Low % drift is often whitespace/comment artifacts the package already strips — re-run with--normalizeto rule those out. - ORPHANED — never trust the orphan label alone. Cross-check:
composer show --name-only— is the originating package actually gone?grep -rthe filename/key acrossapp/,config/,routes/,bootstrap/— is the file still referenced?git log -- <path>— was it user-authored orvendor:publish-generated? An "orphan" may be a deliberately app-owned file that lives inconfig/or a sibling of a package that publishes under a different basename.
- MISSING — confirm the vendor source file actually belongs to a publish group the user intends to consume. Some vendor files in
vendor/*/config/are internal and never meant to be published.
If any verification step contradicts the package's category, trust your verification — report the discrepancy to the user and adjust the recommendation.
When to use this skill
Invoke when the user wants to:
- Find files left behind after a package was removed (orphans).
- Detect drift between locally published config/migration/view/lang files and the upstream vendor copy.
- Identify unchanged published files that can be safely deleted to fall back to vendor defaults.
- Audit before a Laravel/package upgrade so they know exactly what they have customized.
- Reduce repository "cruft" from
vendor:publishoperations.
Do not invoke for general dependency cleanup (composer remove, composer prune) - this skill is scoped to published vendor files only.
Prerequisites
Before running any command, verify:
- The package is installed:
grep leek/laravel-vendor-cleanup composer.json(or checkcomposer.lock). If absent, suggest:composer require leek/laravel-vendor-cleanup --dev - The user is in a Laravel project root (
artisanfile exists, Laravel 11+ / PHP 8.2+). - The
vendor/directory is present and populated (composer installhas been run).
Commands
All four commands share the same flags and output four categories. Pick the command that matches what the user is auditing.
| Audit target | Command | What it scans |
|---|---|---|
| Config files | php artisan vendor-cleanup:config |
config/*.php vs vendor/*/config/*.php |
| Migrations | php artisan vendor-cleanup:migration |
database/migrations/*.php vs vendor migrations (timestamp-stripped match) |
| Lang files | php artisan vendor-cleanup:lang |
lang/vendor/**/* vs vendor lang files |
| Blade views | php artisan vendor-cleanup:view |
resources/views/vendor/**/*.blade.php vs vendor views |
Shared flags
--normalize- Also normalizes whitespace and line endings before comparison. PHP comments are always stripped. Use when whitespace-only diffs (line endings, indentation tweaks) are showing files as modified that the user considers identical.--delete- Prompts to delete the UNCHANGED files after displaying results. Destructive - see safety section below.
Workflow
Run commands read-only first (no --delete). Treat output as a list of paths to investigate. Verify each path independently. Only after verification, propose actions.
Gather leads — run relevant audit(s) with no flags:
php artisan vendor-cleanup:config php artisan vendor-cleanup:migration php artisan vendor-cleanup:lang php artisan vendor-cleanup:viewIf user did not specify a target, run all four. Capture the file paths under each category. Do not relay the package's verdict to the user yet.
Verify every flagged file yourself. For each path the package reported:
- UNCHANGED candidates —
Readbothvendor/<source>and the local copy. Confirm content equivalence beyond what the hash check sees (e.g., logically equivalent arrays, intentional value-pinning). Only files that pass your own check are real cruft. - MODIFIED candidates — produce a real diff:
Read the diff. Classify drift as intentional (env wiring, custom values, app logic) or accidental (stale copy, partial merge, formatting). For low-% items, re-run the relevant command withdiff -u <vendor-source-path> <local-path>--normalizeand see if it drops out — confirms whitespace-only noise. - ORPHANED candidates — verify via three checks before treating as deletable:
File is a true orphan only if: originating package absent fromcomposer show --name-only | sort grep -rn "<filename-without-ext>" app/ config/ routes/ bootstrap/ database/ resources/ git log --oneline -- <path>composer show, no live references in app code, and git history shows it arrived viavendor:publish(not user-authored). Otherwise treat as app-owned and leave alone. - MISSING candidates — open
vendor/<package>/composer.jsonand inspectextra.laravel.providers+ the service provider'sboot()to confirm the file is actually in a publish group the user would want. Skip files that are package-internal.
- UNCHANGED candidates —
Synthesize verified findings. Drop anything that failed verification. Report only verified results to the user, with the evidence you collected (diff snippets, grep counts, git log lines). Example:
Verified 4 UNCHANGED configs against vendor sources:
cache.php,mail.php,session.php,filesystems.php— content-equivalent.config/old-dependency.phpflagged ORPHANED, confirmed: package absent fromcomposer show, zero references in app code, last touched byvendor:publishcommitabc1234.Then propose actions. For UNCHANGED-and-verified, suggest the relevant
--deleteinvocation. For ORPHANED-and-verified, propose explicitrm <path>(orgit rm). For MODIFIED, surface the diff so the user decides.
Safety
- Never run
--deletewithout first running the read-only command and showing the user the UNCHANGED list. --deleteonly removes UNCHANGED files (never MODIFIED or ORPHANED). Orphan removal is the user's call - if requested, delete with explicitrmafter confirming each path.- Migrations: deleting an UNCHANGED published migration is safe only if it has not yet run in production, or the vendor package will continue to provide it via auto-loaded migrations. Warn the user before deleting any migration file. Check
php artisan migrate:statusif uncertain. - Always recommend the user has a clean git working tree (
git status) before any deletion so changes are revertable.
Worked example
User: "Audit my vendor configs and tell me what's drifted."
Step 1 — gather leads:
php artisan vendor-cleanup:config
Sample raw output (treat as worklist only):
MODIFIED
| config/services.php | 65.3% |
| config/app.php | 38% |
| config/database.php | 9.8% |
UNCHANGED (matches vendor)
| config/filesystems.php | config/mail.php |
| config/cache.php | config/session.php |
ORPHANED (no vendor counterpart)
| config/old-dependency.php |
Step 2 — verify each path independently:
# MODIFIED: real diffs, not similarity scores
diff -u vendor/laravel/framework/config/services.php config/services.php
diff -u vendor/laravel/framework/config/app.php config/app.php
diff -u vendor/laravel/framework/config/database.php config/database.php
# Whitespace-only? confirm by re-running normalized:
php artisan vendor-cleanup:config --normalize
# UNCHANGED: re-read and confirm equivalence
# (Read tool on each pair)
# ORPHANED: confirm package is truly gone and file unreferenced
composer show --name-only | grep -i old-dependency || echo "package absent"
grep -rn "old-dependency" app/ config/ routes/ bootstrap/ database/ resources/
git log --oneline -- config/old-dependency.php
Step 3 — only verified findings reach the user:
services.php65% drift confirmed bydiff -u: custom Stripe/Mailgun keys + newpusherblock. Real customization, keep.app.php38% drift confirmed: provider list + locale customized. Keep.database.php9.8% drift drops to 0 under--normalize: whitespace-only. Treat as UNCHANGED.filesystems.php,mail.php,cache.php,session.php: read both sides — content-equivalent. Real cruft.old-dependency.php:composer showempty for that vendor, zerogrephits, last commit wasvendor:publish2 years ago. Real orphan.
Step 4 — propose actions:
php artisan vendor-cleanup:config --deletefor the 4 UNCHANGED +database.php.git rm config/old-dependency.phpfor the verified orphan.- Leave
services.phpandapp.phpalone — document drift in upgrade notes.
Limitations
- Comparison is content-based (SHA256 +
similar_text). Rename-only refactors in vendor packages may show as MODIFIED even when semantically identical. - Migrations match by basename with timestamp stripped (
2024_01_15_123456_create_jobs_table.php→create_jobs_table.php). Custom-named migrations that shadow vendor ones may misclassify - inspect manually. - The package only inspects files under the four supported roots. It does not detect orphans under arbitrary paths (e.g.
resources/js/vendor/). - Requires PHP 8.2+ and Laravel 11.x / 12.x.
Source: leek/laravel-vendor-cleanup — distributed by TomeVault.