tightenco/duster
Duster automatically applies Tighten's default Laravel code style by combining TLint, PHP_CodeSniffer, PHP CS Fixer, and Pint. Lint or fix everything at once, target only uncommitted files with --dirty, and customize includes/excludes via duster.json.
Architecture fit
Duster is a dev-only Composer package (Laravel Zero CLI application) that acts as a thin orchestrator over four independently maintained style tools: TLint, PHP_CodeSniffer, PHP CS Fixer, and Pint. Architecturally it is a build-time/developer-experience layer, not an application dependency — it installs with --dev, never executes in a request lifecycle, and ships no runtime code. That makes it a near-zero-blast-radius addition to a Laravel codebase: the worst-case failure is a noisy CI job, not a production incident.
The design decision that matters most is that Duster does not reimplement linting; it bundles pinned versions of upstream tools plus Tighten's curated presets (Tighten for TLint/PHPCS, Laravel preset for Pint, plus a CustomOrderedClassElementsFixer rule for class element ordering). Value is therefore concentrated in preset curation and orchestration ergonomics, not in novel capability. A team could replicate ~80% of the behavior by wiring the four tools together manually. This is the core build-vs-adopt tradeoff a TPM should price explicitly: Duster is buying you a maintained opinion about style, plus a single command surface (duster lint / duster fix) and a --dirty fast path. Both are real developer-productivity wins, but they are convenience, not differentiated technology.
Integration feasibility
High. Installation is a single Composer require, execution is ./vendor/bin/duster, and it degrades gracefully: --using="phpstan,tlint,pint" lets you compose which tools run and in what order, and duster.json gives include/exclude globs, custom scripts (arbitrary Symfony\Process invocations with a configurable processTimeout), which means static analysis and other gates can be folded into the same entry point. It publishes GitHub Actions and Husky hook stubs out of the box, and knows how to shell into containerized dev environments (ddev, warden, lando, sail) via --env. The --dirty mode (lint only files with uncommitted changes) is the key ergonomic feature for incremental adoption and for keeping pre-commit hooks fast.
The friction points are opinionation and configuration drift. The defaults are Tighten's house style, and per-tool customization requires creating and maintaining four separate config files (tlint.json, .phpcs.xml.dist, .php-cs-fixer.dist.php, pint.json) whose contents are copied from Duster's standards/ directory. That is a copy-and-drift surface: once you fork the standard, upgrades to Duster no longer meaningfully update your rules.
Technical risk
Low-to-moderate, and almost entirely toolchain risk rather than security or runtime risk:
git blame and can collide with open PRs. This is the single most disruptive failure mode in practice.laravel/pint or friendsofphp/php-cs-fixer, Composer resolution can force a Duster downgrade or block upgrades. Recent releases show active Guzzle/PHPCS bumps, so this is a live maintenance treadmill, not a hypothetical.duster-action commits fixes back to the repo; the README warns these commits stop other running workflows and do not trigger them. Getting the workflow_run orchestration wrong silently disables downstream checks — a correctness risk, not just an annoyance.Dependents: 0 and a blank star count are almost certainly artifacts of how dev-dependencies are (not) graphed, so they should not be read as "unused." But they do mean you cannot outsource the due-diligence to adoption metrics — validate maintenance health directly from the release history and PR activity.Key questions
Tighten preset, or will we spend the first sprint rewriting four config files to diverge from it?main/feature branches, or must fixes be surfaced as a failing check only?--env allowlist (ddev, warden, lando, sail) or do we need custom invocation?Stack fit
Strong fit for a standard Laravel/PHP application with Composer, Git, and GitHub Actions, particularly one already using Sail or a supported container tool. Poor fit for polyglot monorepos with mixed PHP/JS/Go services, or for teams that want a single cross-language formatter story — Duster is PHP-only and Laravel-shaped. It also presumes Git for --dirty, so it does not compose with non-Git VCS workflows.
Migration path
Adopt in report-only mode before any autofix. Concretely: (1) require the package on a branch, (2) run duster lint with no config to quantify the existing debt as a finding count, not a fix, (3) decide whether to converge on the Tighten preset or fork configs and record that decision, (4) apply duster fix once in a single dedicated "style baseline" commit with no logic changes, added to .git-blame-ignore-revs, (5) flip CI to a blocking gate. Step 4 as its own commit is the mitigation for the blame-churn problem and is worth enforcing as policy.
Compatibility
Verify PHP constraint alignment first, then check for existing laravel/pint or friendsofphp/php-cs-fixer requirements in composer.json that could conflict. Prefer letting Duster own the tool versions and removing direct pins, rather than fighting resolution. Pin Duster itself to a known-good minor and upgrade deliberately, treating each upgrade as a rule-change review, not a routine bump. The published config files in standards/ should be copied once and diffed on every upgrade to see what upstream changed.
Sequencing
lint runtime and finding count..git-blame-ignore-revs.--dirty pre-commit hook (Husky or equivalent) for fast local feedback.duster.json scripts so a single command covers all gates; tune processTimeout if the 60s default is too tight.duster-action, with the workflow_run trigger fix in the same PR.Maintenance
Low ongoing effort but a persistent low-grade upgrade tax. Roughly half the recent release notes are dependency bumps, meaning Duster's release cadence is coupled to upstream churn you would partially inherit anyway if you ran the tools directly. Budget one minor upgrade per quarter with a ruleset-diff review, and treat major versions as scheduled work items. Config-file divergence is the hidden cost: the more you customize, the more you own.
Support
Community-supported via GitHub issues/PRs under an MIT license with Tighten's commercial interest behind it. No SLA, no LTS branch, no formal deprecation policy. Acceptable for a dev tool; do not let it become a single point of knowledge — ensure at least two engineers can explain the config.
Scaling
Per-developer cost is near zero (single binary invocation). CI cost scales with repository size and matters more: full-repo lint runs PHPCS over every file and grows linearly with codebase size. Mitigation is --dirty locally and sharding or path-scoping in CI (duster.json include/exclude, or --using to drop the slowest tool). Across many repos, standardization is the win — one preset, one command, one gate — but also the risk: a bad preset change propagates to every repo at once, so roll Duster upgrades through a canary repo first.
Failure modes
workflow_run triggers, or avoid auto-commit entirely.fix — Duster's own release notes added a hint to run lint for this reason; expect a class of issues requiring human judgment.--dirty edge cases (untracked files, renames, detached HEAD in CI) producing empty or unexpected file sets; note v1.1.1 specifically fixed a non-zero exit when no dirty files exist.Ramp-up
Very fast for anyone who has used Pint or PHPCS: one command to learn, one optional config file, and a --using flag for composition. Plan for ~30 minutes of onboarding per engineer, plus a short written note on the two things people get wrong — do not run fix on a dirty working tree without committing first, and do not assume fix resolves everything (PHPCS findings often need manual work). The main ramp-up cost is organizational, not individual: agreeing on the preset and the baseline commit before the first gate goes live.
How can I help you explore Laravel packages today?