lucasp1337/laravel-loom
Laravel Loom is a small Laravel package that aims to make building and organizing application features simpler, with a lightweight API and sensible defaults. It is designed to slot into existing Laravel projects without heavy setup or boilerplate, giving developers a straightforward tool they can ad...
Loom statically scans a Laravel application and emits a versioned JSON model of its routes, event dispatches, listeners/handlers, closures, and model events. In v0.4.0 the output schema and MCP surface are frozen at 1.0, so it is safe to build tooling against them.
Requirements and install:
# v0.4.0 drops Laravel 11 — you need Laravel 12+
composer require lucasp1337/laravel-loom
# Optional integrations, install only if you use them
composer require livewire/livewire # Livewire-aware scanning
composer require laravel/mcp # expose Loom over MCP
Publish the config, then run the scanner:
php artisan vendor:publish # select the Loom service provider
php artisan loom:scan --help # inspect path/output options added in 0.4.0
php artisan loom:scan
Where to look first:
scan paths to your app/, routes/, and any domain/module directories.First useful use case: run a scan on a branch before a refactor and keep the JSON as a baseline. Diffing routes/dispatches/model events between branches is the fastest way to catch an accidentally removed listener or a route whose middleware changed.
Baseline diffing in CI. Commit a scan artifact per release branch and diff it in pull requests. Because schema 1.0 is frozen, the diff is stable and reviewable. A dropped route, a changed dispatch target, or a new model_events entry becomes a reviewable line rather than a runtime surprise.
Scope scans per module. Use the configurable scan paths (new in 0.4.0) to scan one domain at a time in large monoliths. This keeps runtime down and makes ownership obvious in the output.
// config/loom.php (shape illustrative — confirm keys against the published file)
'scan' => [
'paths' => [
app_path('Domain/Billing'),
base_path('routes/billing'),
],
],
Dispatch execution-mode auditing. dispatch sites now record their execution mode. Use this to find events dispatched synchronously on request-critical paths — those are your latency and failure-coupling risks — versus queued dispatches that are safer to fan out.
Route discovery for provider-loaded route files. 0.4.0 discovers route files loaded by providers and inherits group attributes (middleware, prefixes, names) into them. If you split routes across service providers, you no longer need to duplicate route registration just so Loom sees it. Verify that your custom Route::group wrappers are inside a provider file the scanner can follow.
Closure analysis. Closure ownership, closure routes, dispatchesEvents, and inherited handlers are now modelled. This matters for older codebases where routes are defined with inline closures and Eloquent models use $dispatchesEvents instead of explicit Event::dispatch calls. Treat those entries as "best-effort static resolution" and spot-check the important ones.
MCP / Boost for agent workflows. With laravel/mcp installed, expose the scan over MCP and install the shipped Boost guidelines/skill so AI assistants query the real route and dispatch graph instead of guessing. This is the highest-leverage daily use: ask "what fires when an order is refunded?" and get an actual traversal, not a hallucination.
Guard the public PHP API boundary. The scanner ADR defines a public API boundary, and optional-package classes are tagged @internal. Build integrations against the JSON schema and MCP surface, not against scanner internals.
composer update.livewire/livewire, Livewire components simply will not appear in the scan — no error, just missing data. Same for the MCP surface without laravel/mcp.@internal classes. Classes tied to optional packages are explicitly marked internal. They can move or change shape in a patch release even though the schema is frozen.Event::dispatch($class), container-resolved class strings, variable middleware), runtime route registration, and macros are generally invisible. Fixes in 0.4.0 cover Event facade aliases and dispatchIf/dispatchUnless seeding — treat that as evidence of the class of bug, not a guarantee of completeness.middleware() calls on a registrar now replace the group middleware rather than stacking it. If you were relying on the previous (incorrect) accumulation in your scan output, expect diffs.loadRoutesFrom/provider-based loading, or add the path explicitly.schema_version field in any consumer before parsing, and fail loudly on a mismatch rather than silently mis-reading fields.How can I help you explore Laravel packages today?