vortech / laravel-fuse
Track and enforce temporary code and technical debt in Laravel applications.
Requires
- php: ^8.4
- illuminate/console: ^13.0
- illuminate/contracts: ^13.0
- illuminate/support: ^13.0
- nikic/php-parser: ^5.0
- symfony/finder: ^7.0|^8.0
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pint: ^1.0
- orchestra/testbench: ^11.0
- pestphp/pest: ^5.0
- pestphp/pest-plugin-laravel: ^5.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Track and enforce temporary code and technical debt with expiring PHP attributes.
When the fuse runs out, your CI fails.
Why
Temporary code has a habit of becoming permanent. A // TODO remove later comment is invisible to your tooling, so nobody is ever reminded. Fuse makes the debt machine-readable:
// TODO remove later |
#[Fuse] |
|
|---|---|---|
| Says why the code exists | maybe | required (reason) |
| Has a deadline | no | required (expires) |
| Has an owner and a ticket | no | optional, can be made mandatory |
| Fails CI when it is overdue | no | yes (fuse:check) |
| Production overhead | none | none |
Fuse reads your source statically. It never boots, loads or instantiates your classes, and nothing runs at request time.
Requirements
- PHP 8.4+
- Laravel 13
Installation
composer require vortech/laravel-fuse --dev
The service provider is registered automatically through package discovery. Then run the install command to publish the config file:
php artisan fuse:install
Use --force to overwrite an existing config file.
PHP only instantiates attributes when something reads them through reflection, so a --dev install is safe in production even though your classes carry #[Fuse] attributes.
Usage
1. Mark temporary code
use Vortech\Fuse\Attributes\Fuse; #[Fuse( reason: 'Temporary compatibility layer for NAV API v3', expires: '2026-12-01', owner: 'integrations', issue: 'FIS-142', )] final class NavGateway { // }
The attribute works on classes, methods, properties (including promoted constructor properties) and functions, and it is repeatable:
final class InvoiceService { #[Fuse( reason: 'Temporary retry logic until billing provider fixes timeout issue', expires: '2026-11-30', owner: 'billing', issue: 'BILL-482', severity: FuseSeverity::High, type: FuseType::Workaround, replacement: 'Provider native retry handling', )] private function retryLegacyRequest(): void { // } }
2. Check it in CI
php artisan fuse:check
Fuse check
✓ 18 active
⚠ 3 expiring
✗ 2 expired
FAIL
FAILING
[HIGH] app/Services/NavGateway.php:12
Temporary compatibility layer for NAV API v3
Expired 2026-12-01
Target App\Services\NavGateway
Owner integrations
Issue FIS-142
That is all. There is no database to set up and no dashboard to run.
Metadata
| Argument | Required | Description |
|---|---|---|
reason |
yes | Why the code exists. |
expires |
yes | Expiration date, strictly YYYY-MM-DD. Relative dates such as tomorrow are rejected. |
owner |
no | Person, team or domain responsible. |
issue |
no | Ticket or issue reference, e.g. FIS-142, #142 or a URL. |
severity |
no | FuseSeverity::Low, Medium (default), High or Critical. |
type |
no | FuseType::TechnicalDebt (default), Workaround, Temporary, Deprecated, Migration or Fallback. |
replacement |
no | What is going to replace the code. |
created |
no | Creation date, YYYY-MM-DD. |
Values must be literals (strings, null and enum cases), because Fuse reads your code statically instead of running it.
An item expires on its expires date: from that day on it counts as expired. Items inside the warning window (warn_within_days, 14 by default) count as expiring.
Commands
All commands accept --no-cache to ignore the scan cache.
fuse:check
The command to run in CI.
php artisan fuse:check php artisan fuse:check --fail-within=7 php artisan fuse:check --severity=high php artisan fuse:check --format=json php artisan fuse:check --format=github
| Exit code | Meaning |
|---|---|
0 |
Success |
1 |
An item expired (or expires within --fail-within days) at or above the severity threshold |
2 |
Invalid Fuse metadata or configuration: bad date, unknown enum case, missing required owner or issue |
3 |
Scanner failure, e.g. a file that cannot be parsed |
--fail-within=Nalso fails for items that expire within N days. Useful on release branches. Defaults tofuse.fail_within_days.--severity=only lets items of at least that severity fail the check. Defaults tofuse.fail_at_severity.- Expired items below the threshold, and items that are merely expiring, are reported as warnings.
--format=githubprints workflow commands, so GitHub Actions shows annotations directly on the source files.--format=jsonprints a machine-readable report.
fuse:list
php artisan fuse:list
php artisan fuse:list --expired
php artisan fuse:list --expiring=14
php artisan fuse:list --owner=payments
php artisan fuse:list --severity=high # high and critical
php artisan fuse:list --type=workaround
php artisan fuse:list --json
fuse:install
Publishes config/fuse.php. Pass --force to overwrite an existing file.
fuse:doctor
Checks the setup and the metadata: configuration, scan paths, parser failures, invalid dates, unsupported enum values, missing owners or issues, malformed issue references, duplicated attributes and items that have been expired for over a year.
fuse:stats
Totals plus breakdowns by status, severity, type and owner.
GitHub Actions
name: Fuse on: pull_request: push: branches: [main] jobs: fuse: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 with: php-version: '8.4' - run: composer install --no-interaction --prefer-dist - run: php artisan fuse:check --format=github --fail-within=7
Expired items become errors, items about to expire become warnings.
Configuration
Publish the config with php artisan fuse:install (or php artisan vendor:publish --tag=fuse-config), then edit config/fuse.php:
| Key | Default | Description |
|---|---|---|
paths |
[app_path()] |
Directories or files to scan. |
ignore |
storage, vendor, bootstrap/cache | Paths to skip. |
warn_within_days |
14 |
Items expiring within this window are "expiring". |
fail_within_days |
0 |
fuse:check also fails for items expiring within this window. |
fail_at_severity |
FuseSeverity::Low |
Minimum severity that can fail CI (enum case or string). |
require_owner |
false |
Items without owner are invalid. |
require_issue |
false |
Items without issue are invalid. |
cache.enabled |
true |
Cache parsed metadata per file. |
cache.store |
null |
Cache store to use. null is the default store. |
cache.ttl |
3600 |
Cache lifetime in seconds. |
Programmatic API
use Vortech\Fuse\FuseManager; $fuse = app(FuseManager::class); $fuse->scan(); // FuseCollection of every item $fuse->expired(); $fuse->expiringWithin(14); $fuse->result(); // ScanResult: items, problems and the number of files scanned $fuse->check(); // CheckReport, what fuse:check works with
The Vortech\Fuse\Facades\Fuse facade exposes the same methods. FuseCollection is immutable and offers expired(), expiring(), active(), expiringWithin($days), critical(), severityAtLeast($severity), ownedBy($owner) and ofType($type).
Good to know
- Static analysis only. Fuse parses your files with nikic/php-parser. Scanning has no side effects and needs no booted dependencies. Runtime enforcement is intentionally not part of Fuse.
- Scan cache. Parsed metadata is cached per file and invalidated when the file's modification time or size changes. Statuses are always computed fresh, so a cached item still expires on time.
- Invalid attributes are errors, not silence. A
#[Fuse]with a malformed date or an unknown enum case is reported as a problem and failsfuse:checkwith exit code2.
Testing
composer test
composer analyse
composer format
The suite uses Pest and requires PHP 8.4+ to run.
Changelog
See CHANGELOG for what has changed recently.
Security
If you discover a security issue, please email mate@vortech.hu instead of using the issue tracker.
Credits
- Mate Papp, Developer @ Vortech
License
The MIT License (MIT). See the License File for more information.