limenet / laravel-baseline
A highly opinionated Laravel baseline.
Fund package maintenance!
Requires
- php: ^8.3
- ext-simplexml: *
- composer/semver: ^3.4
- illuminate/contracts: ^12.0 || ^13.0
- laravel/prompts: ^0.3 || ^1.0
- nesbot/carbon: ^3.0
- nikic/php-parser: ^5.7
- phpstan/phpstan: ^2.2.2
- rector/rector: ^2.5.8
- spatie/laravel-package-tools: ^1.93
- symfony/console: ^7.4 || ^8.0
- symfony/finder: ^7.4 || ^8.0
- symfony/yaml: ^7.4 || ^8.0
Requires (Dev)
- larastan/larastan: ^3.12.2
- laravel/framework: ^12.53.0 || ^13.33.0
- laravel/pint: ^1.32.1
- mockery/mockery: ^1.6.15
- nunomaduro/collision: ^8.9.5
- orchestra/testbench: ^10.9.0 || ^11.3
- pestphp/pest: ^4.7.7 || ^5.2.1
- pestphp/pest-plugin-arch: ^4.0.2 || ^5.0.0
- pestphp/pest-plugin-laravel: ^4.1 || ^5.0.1
- phpstan/extension-installer: ^1.4.3
- phpstan/phpstan-deprecation-rules: ^2.0.5
- phpstan/phpstan-phpunit: ^2.0.18
- spatie/laravel-health: ^1.40.2
- spatie/temporary-directory: ^2.4.0
- symplify/rule-doc-generator-contracts: ^11.2
Suggests
None
Provides
None
Conflicts
- laravel/boost: ^1.0
Replaces
None
- dev-main
- 2.17.0
- 2.16.0
- 2.15.1
- 2.15.0
- 2.14.0
- 2.13.0
- 2.12.2
- 2.12.1
- 2.12.0
- 2.11.0
- 2.10.0
- 2.9.0
- 2.8.1
- 2.8.0
- 2.7.0
- 2.6.0
- 2.5.0
- 2.4.4
- 2.4.3
- 2.4.2
- 2.4.1
- 2.4.0
- 2.3.2
- 2.3.1
- 2.3.0
- 2.2.0
- 2.1.5
- 2.1.4
- 2.1.3
- 2.1.2
- 2.1.1
- v2.1.0
- v2.0.13
- v2.0.12
- v2.0.11
- v2.0.10
- v2.0.9
- v2.0.8
- v2.0.7
- v2.0.6
- v2.0.5
- v2.0.4
- v2.0.3
- v2.0.2
- v2.0.1
- v2.0.0
- 1.3.6
- 1.3.5
- 1.3.4
- 1.3.3
- 1.3.2
- 1.3.1
- 1.3.0
- 1.2.25
- 1.2.24
- 1.2.23
- 1.2.22
- 1.2.21
- 1.2.20
- 1.2.19
- 1.2.18
- 1.2.17
- 1.2.16
- 1.2.15
- 1.2.14
- 1.2.13
- 1.2.12
- 1.2.11
- 1.2.10
- v1.2.9
- v1.2.8
- v1.2.7
- v1.2.6
- v1.2.5
- v1.2.4
- v1.2.3
- v1.2.2
- v1.2.1
- v1.2.0
- v1.1.12
- v1.1.11
- v1.1.10
- v1.1.9
- v1.1.8
- v1.1.7
- v1.1.6
- v1.1.5
- v1.1.4
- v1.1.3
- v1.1.2
- v1.1.1
- v1.1.0
- v1.0.0
- v0.2.12
- v0.2.11
- v0.2.10
- v0.2.9
- v0.2.8
- v0.2.7
- v0.2.6
- v0.2.5
- v0.2.4
- v0.2.3
- v0.2.2
- v0.2.1
- v0.2.0
- v0.1.19
- v0.1.18
- v0.1.17
- v0.1.16
- v0.1.15
- v0.1.14
- v0.1.13
- v0.1.12
- v0.1.11
- v0.1.10
- v0.1.9
- v0.1.8
- v0.1.7
- v0.1.6
- v0.1.5
- v0.1.4
- v0.1.3
- v0.1.2
- v0.1.1
- v0.1.0
- dev-fix/gitlab-ci-custom-tags
- dev-feat/standalone-php-profiles
This package is auto-updated.
Last update: 2026-10-09 08:12:23 UTC
README
Checks your Laravel installation against a highly opinionated baseline.
This repository ships two runners from one policy:
| Composer (Laravel) | Composer (standalone) | npm | |
|---|---|---|---|
| Package | limenet/laravel-baseline |
limenet/laravel-baseline |
@limenet-ch/baseline |
| For | Laravel projects (DDEV, composer) | other PHP projects, e.g. WordPress themes | JS/TS-only projects (no PHP, no DDEV) |
| Command | php artisan limenet:laravel-baseline:check |
vendor/bin/baseline check |
npx baseline check |
| Checks | all of them | the php / wordpress profiles |
the portable subset |
Both read policy/, so the version floors and required keys are defined once, and both are
executed against the shared behavioural fixtures in fixtures/. They are released in lockstep:
the same version number means the same policy in both ecosystems.
Installation
You can install the package via composer:
composer require limenet/laravel-baseline
You can publish the config file with:
php artisan vendor:publish --tag="laravel-baseline-config"
Usage
Add to your composer.json to run checks (and auto-fix) after every composer update:
"post-update-cmd": [ "@php artisan limenet:laravel-baseline:check --fix" ],
Running checks
# Check only β report issues without making changes php artisan limenet:laravel-baseline:check # Auto-fix β apply all safe automatic fixes, then report remaining issues php artisan limenet:laravel-baseline:check --fix
Checks marked π§ below support --fix. When --fix is used:
- Fully fixable checks: all conditions are applied automatically.
- Partially fixable checks (requires package installed first): configuration/script entries are fixed once the package is installed via
composer require. - Non-fixable checks: report the issue with an actionable message.
AI guidelines & skills
The package also ships Laravel Boost resources under
resources/boost/: an always-on guideline (the dev loop β ci-lint, tests, DDEV-first
conventions) and on-demand skills (e.g. creating-a-release). When a project that has
laravel/boost installed runs php artisan boost:install or php artisan boost:update --discover,
Boost discovers and publishes these to the consuming project's coding agents automatically.
Non-Laravel PHP projects
The same composer package also checks PHP projects that are not Laravel apps, through
vendor/bin/baseline instead of artisan. It runs the checks that make sense without Laravel β
composer scripts, PHPStan, Pint, plain Rector sets, DDEV, CI, editor and Claude settings β and
skips everything that needs artisan, config/*.php or a Laravel package.
composer require --dev limenet/laravel-baseline
ddev exec vendor/bin/baseline check # report issues ddev exec vendor/bin/baseline check --fix # apply safe fixes, then report what is left ddev exec vendor/bin/baseline periodic # walk through expired periodic checks
Run it after every composer update, like the Laravel runner:
"post-update-cmd": [ "@php vendor/bin/baseline check --fix" ],
The runner picks a profile from the project:
| Profile | Detected by |
|---|---|
wordpress |
a style.css with a Theme Name: header, a root *.php with a Plugin Name: header, or a composer type of wordpress-theme / wordpress-plugin / wordpress-muplugin |
php |
anything else |
A Laravel application (an artisan file, or laravel/framework in require) is refused β use the
artisan command there, which runs the full Laravel profile. Set "profile": "php" or
"profile": "wordpress" in .baseline.json to override the detection.
Outside Laravel a test suite is optional: the Pest, phpunit.xml and pcov checks report a warning
instead of failing until pestphp/pest is installed, and the CI test job is not required.
State lives in .baseline.json at the project root, the same file and shape the npm runner uses:
{
"excludes": ["hasTrivyConfig"],
"periodic": { "updatesDependencies": "2026-08-16T09:00:00+00:00" }
}
What the standalone runner checks
Every check below also runs in Laravel projects unless marked standalone-only.
| Check | php |
wordpress |
Relationship to the Laravel runner |
|---|---|---|---|
allowsToolingInClaudeSettings |
β | β | requires the shared and ddev composer allow entries, not the artisan ones |
biomeIgnoresCiArtifacts |
β | β | identical |
biomeUsesLocalSchema |
β | β | identical |
bumpsComposer |
β | β | identical |
callsBaseline |
β | β | hooks @php vendor/bin/baseline check --fix into post-update-cmd instead of the artisan command |
checkPhpunit |
β | β | warns until a test suite exists; requires only the cobertura and JUnit reports, not APP_KEY or the ./app source |
ddevHasPcovPackage |
β | β | warns until pestphp/pest is installed |
ddevMutagenIgnoresNodeModules |
β | β | identical |
ddevNodeVersionIsAuto |
β | β | identical |
deniesEnvReadsInClaudeSettings |
β | β | identical |
doesNotCallPeriodicBaselineOnUpdate |
β | β | flags vendor/bin/baseline periodic instead of the artisan command |
doesNotExcludeUnknownChecks |
β | β | reads .baseline.json, against this profileβs checks |
doesNotHaveCopilotOrJunieAgentFiles |
β | β | identical |
doesNotUseBothBaselineRunners |
β | β | identical |
doesNotUseGreaterThanOrEqualConstraints |
β | β | identical |
doesNotUsePhpCsFixer |
β | β | identical |
doesNotUsePhpInsights |
β | β | identical |
hardensNpmSupplyChain |
β | β | identical |
hasCiJobs |
β | β | same GitLab CI templates, without the test job |
hasEditorconfig |
β | β | identical |
hasNpmScripts |
β | β | identical |
hasRectorConfigWithAttributesSets |
β | β | identical |
hasRectorConfigWithImportNames |
β | β | identical |
hasRectorConfigWithPestSet |
β | β | identical |
hasRectorConfigWithPhpSets |
β | β | identical |
hasRectorConfigWithPreparedSets |
β | β | identical |
hasTrivyConfig |
β | β | identical |
isCiLintComplete |
β | β | identical |
isInstalledAsDevDependency |
β | β | standalone-only: the package belongs in require-dev; the Laravel runner's isInstalledAsRegularDependency requires require instead, because a Laravel app loads it at runtime |
nodeVersion |
β | β | identical |
phpVersionMatchesCi |
β | β | identical |
phpVersionMatchesDdev |
β | β | identical |
phpstanCoversAllPhpFiles |
β | β | standalone-only |
phpstanLevelAtLeastEight |
β | β | identical |
rectorCoversAllPhpFiles |
β | β | standalone-only |
releaseItBumpsWordpressThemeVersion |
β | standalone-only, themes only | |
runsCiLintHookInClaudeSettings |
β | β | identical |
updatesDdevAddons |
β | β | identical |
updatesDependencies |
β | β | identical (periodic, every 30 days); recorded in .baseline.json |
usesPest |
β | β | warns until pestphp/pest is installed; does not require pest-plugin-laravel |
usesPestPhpstanPlugin |
β | β | identical |
usesPestRectorPlugin |
β | β | identical |
usesPhpstanExtensions |
β | β | identical |
usesPhpstanWordpress |
β | standalone-only | |
usesRector |
β | β | requires rector/rector only, not driftingly/rector-laravel |
usesReleaseIt |
β | β | identical |
wordpressThemeVersionMatchesComposer |
β | standalone-only, themes only |
Deliberately not run outside Laravel: everything artisan-, config/*.php-, schedule- or
Laravel-package-shaped (Horizon, Pulse, Telescope, Spatie Health, Boost, IDE helpers, Larastan,
rector-laravel), and hasRectorConfigWithPaths / hasRectorConfigWithComposerBased, whose
values assume Laravel's layout β rectorCoversAllPhpFiles and phpstanCoversAllPhpFiles take their
place.
JS-only projects
Projects with no PHP and no DDEV use the npm runner instead. It is a second implementation, not a
wrapper: PHP cannot reach into a JS project (and by this baseline's own convention npm runs on
the host while artisan runs inside DDEV), so the portable checks are reimplemented in TypeScript and
kept honest by the shared fixtures rather than by shared code.
npm install --save-dev @limenet-ch/baseline
npx baseline check # report issues npx baseline check --fix # apply safe fixes and install/update the skills, then report what is left npx baseline periodic # walk through expired periodic checks npx baseline install-skills # copy the packaged skills into .claude/skills/ without running the checks
Wire it into the ci-lint npm script so CI and the Claude Stop hook both run it β npm has no
post-update-cmd equivalent, and npm 12 blocks dependency lifecycle scripts by default:
"scripts": { "ci-lint": "biome ci . && tsc --noEmit && baseline check" }
State lives in .baseline.json at the project root (a JS project has no config/ directory):
{
"excludes": ["hasNpmScripts"],
"periodic": { "updatesDependencies": "2026-08-16T09:00:00.000Z" }
}
What the npm runner checks
| Check | Relationship to the Laravel runner |
|---|---|
nodeVersion |
identical |
hardensNpmSupplyChain |
also requires .npmrc to exempt @limenet-ch/baseline from the cooldown (min-release-age-exclude[]=@limenet-ch/baseline, or a glob covering it), so a baseline fix lands without the 7-day wait |
hasEditorconfig |
identical |
biomeUsesLocalSchema |
identical |
biomeIgnoresCiArtifacts |
identical |
doesNotHaveCopilotOrJunieAgentFiles |
identical |
doesNotUseBothBaselineRunners |
mirrored: fails when composer.json requires limenet/laravel-baseline, since the Composer runner wins |
allowsToolingInClaudeSettings |
requires only the shared allow entries, not the DDEV/artisan ones |
deniesEnvReadsInClaudeSettings |
identical |
runsCiLintHookInClaudeSettings |
hooks npm run ci-lint instead of ddev composer run ci-lint |
updatesDependencies |
identical (periodic, every 30 days) |
hasNpmScripts |
identical |
hasCiJobs |
same GitLab CI templates, without the php job |
hasTrivyConfig |
identical, canonical config included: its vendor/**, storage/logs/ and .ddev/ skips are inert in a JS project |
ciSetsNodeVersion |
npm-only: the Laravel runner does not register it |
isCiLintComplete |
asserts the JS toolchain in the npm script, not pint/phpstan in a composer script: whichever linter is installed (biome for @biomejs/biome, eslint for eslint), failing if neither is, plus tsc once the project has a .ts/.tsx/.mts/.cts file outside node_modules and dot-directories |
callsBaseline |
hooks the ci-lint npm script, since npm has no post-update-cmd |
doesNotExcludeUnknownChecks |
identical, against this runner's smaller registry: a name only the Laravel runner knows is as dead here as one that exists nowhere |
usesReleaseIt |
inverted: fails if @release-it/bumper is configured, because package.json is already release-it's source of truth |
Deliberately not ported: everything composer-, artisan-, Rector-, PHPStan- or Spatie-Health-shaped;
the DDEV checks (ddevNodeVersionIsAuto, ddevMutagenIgnoresNodeModules, β¦); and
hasClaudeSettingsWithLaravelSkills / doesNotHaveLaravelSimplifierInClaudeSettings, which are
vacuous without Laravel.
Checks
This package validates your Laravel installation against the following checks:
Testing & Quality Tools
usesPest()- Validates Pest testing framework is configured (not PHPUnit directly)usesPestPhpstanPlugin()- Validatespestphp/pest-plugin-phpstanis installed when Pest 5+ and PHPStan are both present (warns if not applicable)usesPestRectorPlugin()- Validatespestphp/pest-plugin-rectoris installed when Pest 5+ and Rector are both present (warns if not applicable)- π§
usesRector()- Validates Rector automated code modernization is installed, withdriftingly/rector-laravelconstrained to at least^2.6.1β the release whereLaravelSetProvideris gone and its rules arrive throughLaravelSetList::COMPOSER_BASEDinstead (partial: fixes ci-lint script if packages installed) usesLarastan()- Validates Larastan static analysis tool is configuredusesPhpstanExtensions()- Validates PHPStan extensions are installedusesPhpstanWordpress()- Validatesszepeviktor/phpstan-wordpressis installed, so PHPStan knows WordPress's functions, classes and hooks (wordpress profile only)phpstanCoversAllPhpFiles()- Validates PHPStan'sparameters.pathscover every PHP file the project owns (outsidevendor/,node_modules/, dot-directories and git-ignored paths); files listed inexcludePathscount as deliberately skipped.includesare not followed (php and wordpress profiles)phpstanLevelAtLeastEight()- Validates PHPStan is configured to at least level 8- π§
phpstanParsesModelCastsMethod()- Validatesphpstan.neonsetsparseModelCastsMethod: true:ModelCastsPropertyToCastsMethodRectorrewritesprotected $casts = [...]into acasts(): arraymethod, and without this parameter Larastan reads only the generated@return array<string, string>β not a constant array β so every cast is lost and datetime attributes report as strings (inserts the parameter into theparametersblock) - π§
checkPhpunit()- Validates PHPUnit configuration with coverage reports (adds missing XML nodes and APP_KEY) - π§
hasRectorConfigWithComposerBased()- Validates RectorwithComposerBased(phpunit, symfony, laravel)is configured (appends call to rector.php) - π§
hasRectorConfigWithConfiguredRules()- Validates RectorwithConfiguredRule()calls are present forRouteActionCallableRectorandWhereToWhereLikeRector(appends calls to rector.php) - π§
hasRectorConfigWithPreparedSets()- Validates RectorwithPreparedSets(deadCode, codeQuality, codingStyle, typeDeclarations, privatization, instanceOf, earlyReturn)is configured (appends call to rector.php) - π§
hasRectorConfigWithImportNames()- Validates RectorwithImportNames(importShortClasses: false)is configured (appends call to rector.php) - π§
hasRectorConfigWithPhpSets()- Validates RectorwithPhpSets()is called (appends call to rector.php) - π§
hasRectorConfigWithAttributesSets()- Validates RectorwithAttributesSets()is called (appends call to rector.php) - π§
hasRectorConfigWithRules()- Validates RectorwithRules([MinutesToSecondsInCacheRector, UseForwardsCallsTraitRector])is configured (appends call to rector.php) - π§
hasRectorConfigWithSets()- Validates RectorwithSets([LaravelBaselineSetList::REMOVE_DEFAULT_DOCBLOCKS, LaravelSetList::LARAVEL_*])is configured with all required sets (appends call to rector.php) - π§
rectorCoversAllPhpFiles()- Validates rector.php'swithPaths()(pluswithRootFiles()) cover every PHP file the project owns (outsidevendor/,node_modules/, dot-directories and git-ignored paths); paths inwithSkip()count as deliberately skipped (php and wordpress profiles; adds->withRootFiles()when only root files are uncovered β which directories to process is left to the developer) - π§
hasRectorConfigWithPaths()- Validates RectorwithPaths([app, database, routes, tests])is configured (appends call to rector.php) - π§
hasRectorConfigWithPestSet()- Validates RectorwithSets([PestSetList::CODING_STYLE])is configured when Pest 5+ and Rector are both present (appends call to rector.php; warns if not applicable) - π§
hasRectorConfigWithSkip()- Validates RectorwithSkip()contains required skipped rules (always: 6 Laravel rules plusStringToClassConstantRector, which maps the Laravel 5.2-era string events context-free and so rewrites any matching literal βview('auth.login')becomesIlluminate\Auth\Events\Login::class; Laravel 13+:TablePropertyToTableAttributeRector;AddGenericBuilderToScopesRector, new in rector-laravel 2.6 and shipped inLARAVEL_TYPE_DECLARATIONS, which downgrades an already-correctBuilder<$this>toBuilder<static>;MigrateToSimplifiedAttributeRector, which rewrites workinggetFooAttribute()/setFooAttribute()accessors into a singleAttributemethod; when server.php exists:ServerVariableToRequestFacadeRector) (appends an importedwithSkip()call, or merges the missing classes into one that already exists)
IDE & Developer Tools
- π§
hasEditorconfig()- Validates.editorconfigexists with required settings (root = true,charset,end_of_line,indent_style,insert_final_newline,trim_trailing_whitespace) (creates.editorconfigwith canonical content if missing, and adds missing properties to[*]while keeping the project's own sections if incomplete) - π§
hasClaudeSettingsWithLaravelSkills()- Validates Claude Code settings include the Laravel agent skills plugin and marketplace (creates/merges.claude/settings.json) - π§
doesNotHaveLaravelSimplifierInClaudeSettings()- Fails if the deprecatedlaravel-simplifier@laravelplugin is still enabled in.claude/settings.json(removes the entry) - π§
deniesEnvReadsInClaudeSettings()- Validates.claude/settings.jsonpermissions.denyblocks reading.envplus every environment that ships an encrypted file (each.env.{env}.encryptedin the project root requires denying.env.{env});.env.examplestays readable (merges the deny entries) - π§
allowsToolingInClaudeSettings()- Validates.claude/settings.jsonpermissions.allowincludes the DDEV dev-loop commands (ddev composer run ci-lint,ddev composer test, and safe artisan commands:test,make:*,route:list,about,config:show,ide-helper,optimize:clear,cache:clear,config:clear,route:clear,view:clear) so the dev loop runs without prompts (merges the allow entries) - π§
asksBeforeDestructiveDbCommandsInClaudeSettings()- Validates.claude/settings.jsonpermissions.askrequires a confirmation before the artisan commands that destroy database contents (migrate:fresh,migrate:refresh,migrate:reset,migrate:rollback,db:wipe), covering both theddev artisanandphp artisanforms;askrather thandenyso the developer keeps an approval path (merges the ask entries) - π§
runsCiLintHookInClaudeSettings()- Validates.claude/settings.jsonhas aStophook runningddev composer run ci-lint(appends the hook) - π§
usesIdeHelpers()- Validates Laravel IDE Helper is configured:post-update-cmdrunside-helper:generate,ide-helper:models, andide-helper:meta, and.gitignoreignores the generated_ide_helper.php,_ide_helper_models.php, and.phpstorm.meta.phpfiles (partial: adds post-update scripts and gitignore entries if package installed) - π§
gitignoresLspFiles()- Validates.gitignoreignoresstorage/framework/lsp-*.php, the per-editor files the Laravel language server writes intostorage/framework(appends the entry, creating.gitignoreif missing) usesLaravelAdminer()- Warns if Laravel Adminer database UI is missing (optional), validates TFA confirmation and configuration when installed- π§
usesLaravelBoost()- Validates Laravel Boost AI development tool (partial: fixes boost.json and post-update script if package installed) - π§
laravelBoostMcpUsesDdev()- Validates.mcp.jsonconfigures thelaravel-boostMCP server to useddev artisan boost:mcp(creates/fixes.mcp.json; warns iflaravel/boostnot installed) - π§
doesNotHaveCopilotOrJunieAgentFiles()- Fails ifAGENTS.md, a.juniedirectory, or a.github/skillsdirectory exist β these are generated for the Copilot/Junie Boost agents, which are no longer required (deletesAGENTS.md, the.juniedirectory, and the.github/skillsdirectory) runsBoostUpdate()(periodic, every 30 days) - Warns if Laravel Boost is not installed; when installed, fails until a developer confirms runningphp artisan boost:update --discovervialimenet:laravel-baseline:periodicfollowsModernLaravelIdioms()(periodic, every 30 days) - Fails until a developer confirms running theauditing-laravel-idiomsskill, which audits typed cache getters, BackedEnum cache/session keys, andwhenFilledEnum()for request dataupdatesDependencies()(periodic, every 30 days) - Fails until a developer confirms (vialimenet:laravel-baseline:periodic) that composer & npm dependencies were updated by running theupdating-dependenciesskill β which updates in-constraint packages, reviews changelogs for project impact, and reports semver-blocked majors- π§
usesLimenetPintConfig()- Validates custom Laravel Pint formatting standards (partial: adds post-update script if package installed)
Laravel Features & Monitoring
- π§
usesLaravelHorizon()- Validates Laravel Horizon Redis queue manager (partial: adds ci-deploy-post script if package installed) usesLaravelPennant()- Warns if Laravel Pennant feature flags are missing (optional)- π§
usesLaravelPulse()- Validates Laravel Pulse application monitoring (partial: adds PULSE_ENABLED=false to phpunit.xml if package installed) - π§
cacheAllowsPulseSerializableClasses()- On Laravel 13+ with Pulse installed, validates the top-levelserializable_classesallow-list in config/cache.php permits the classes Pulse round-trips through the cache (stdClass,Illuminate\Support\Collection,Carbon\CarbonImmutable) β the Laravel 13 skeleton ships'serializable_classes' => false, which makes every Pulse card fail in production with "tried to access a property on an incomplete object" (adds the missing classes when the value isfalseor an incomplete array) - π§
usesLaravelTelescope()- Validates Laravel Telescope request debugging (partial: adds post-update script and TELESCOPE_ENABLED=false to phpunit.xml if package installed) usesSpatieHealthSetup()- Validates Spatie Health packages, schedules, s3_health disk, and result store configuration- π§
usesSpatieHealthHasCoreChecks()- Validates core health checks (CacheCheck, CpuLoadCheck, DatabaseCheck, DatabaseConnectionCountCheck, DebugModeCheck, EnvironmentCheck, HorizonCheck, QueueCheck, RedisCheck, ScheduleCheck, UsedDiskSpaceCheck) are registered (adds missing checks to Health::checks() in AppServiceProvider) - π§
usesSpatieHealthHasLaravelVersionCheck()- Validates LaravelVersionCheck is registered in Health::checks() (adds to AppServiceProvider) - π§
usesSpatieHealthHasPhpVersionCheck()- Validates PhpVersionCheck is registered in Health::checks() (adds to AppServiceProvider) - π§
usesSpatieHealthHasReleaseAgeCheck()- Validates ReleaseAgeCheck is registered in Health::checks() (adds to AppServiceProvider) usesSpatieHealthCacheCheckCacheStore()- Validates CacheCheck uses the dedicated 'health-checks' cache store via->driver('health-checks')in AppServiceProvider and config/cache.phpusesSpatieHealthQueueCheckCacheStore()- Validates QueueCheck: DispatchQueueCheckJobsCommand is scheduled everyMinute(), uses the dedicated 'health-checks' file cache store in AppServiceProvider and config/cache.phpusesSpatieHealthQueueCheckHorizonQueues()- Validates QueueCheck registers all queues from config/horizon.php via onQueue() (requires laravel/horizon)usesSpatieHealthScheduleCheckCacheStore()- Validates ScheduleCheck uses the dedicated 'health-checks' cache store in both AppServiceProvider and config/cache.php- π§
usesSpatieHealthScheduleCheckHeartbeat()- Validates ScheduleCheck is configured withheartbeatMaxAgeInMinutes(2)to prevent false positives (appends the call to ScheduleCheck in AppServiceProvider) usesSpatieBackup()- Validates Spatie Backup database backups with comprehensive config validation
Infrastructure & Dependencies
usesPredis()- Validates Predis Redis client is installedisLaravelVersionMaintained()- Validates Laravel 11+ is used- π§
doesNotUseSail()- Validates Sail is NOT used (partial: deletes docker-compose.yml; runcomposer remove laravel/sailmanually) - π§
doesNotUsePhpCsFixer()- Validates PHP CS Fixer is NOT used directly β Pint is the formatter (removes thefriendsofphp/php-cs-fixercomposer.json entry,php-cs-fixerci-lint entries and.php-cs-fixer.cache; removes.php-cs-fixer.php/.php-cs-fixer.dist.phponly once apint.jsonexists, so hand-tuned rules are never lost; runcomposer updateafterward) - π§
doesNotUsePhpInsights()- Validates PHP Insights is NOT used (removes thenunomaduro/phpinsightscomposer.json entry, leftover ci-lint script entries, and config/insights.php; runcomposer updateafterward to sync composer.lock) doesNotUseSpatiePasskeysWithFortify()- Fails if bothspatie/laravel-passkeysandlaravel/fortifyare installed, as they overlap in authentication responsibilitydoesNotUseBothBaselineRunners()- Fails whenpackage.jsonalso declares@limenet-ch/baseline: the npm runner is the fallback for projects this package cannot reach, and in a Laravel project this one wins (reports thenpm uninstallto run; never uninstalls for you)doesNotUseHorizonWatcher()- Validates Spatie Horizon Watcher is NOT installed- π§
doesNotUseGreaterThanOrEqualConstraints()- Validates no>=version constraints in composer.json (use^or~instead) (replaces>=X.Ywith^X.Yin composer.json)
CI/CD & Deployment
hasCiJobs()- Validates GitLab CI pipeline jobs are properly configured (thetestjob may extend either.testor.test_db)- π§
hasTrivyConfig()- Validates Trivy security scanning CI job,trivy.yaml(scanners, skip-files, skip-dirs, ignorefile, cache.dir, telemetry/VEX/dependency-tree flags, andpkg.include-dev-depsso development dependencies are reported instead of silently skipped), presence of.trivyignore.yaml, and.trivycache/in.gitignore(creates/merges trivy.yaml, creates an empty .trivyignore.yaml, appends to .gitignore, and adds CI job) callsSentryHook()- Warns if Sentry error tracking is missing (optional)phpVersionMatchesCi()- Validates PHP version consistency with CI configurationisCiLintComplete()- Validates complete linting pipeline- π§
doesNotUseRectorSetProviders()- Fails whilerector.phpstill passesLaravelSetProvidertowithSetProviders():driftingly/rector-laravel2.6.0 deleted the class, so Rector aborts before doing any work, andwithComposerBased(laravel: true)already loads the rules it used to provide (removes the argument, and the call once nothing is left in it; the orphaned import is left to Pint/Rector) - π§
doesNotDuplicateRectorSetRules()- Fails whilerector.phplistsAddGenericReturnTypeToRelationsRectorinwithRules(): it already ships inLaravelSetList::LARAVEL_TYPE_DECLARATIONS, whichhasRectorConfigWithSets()mandates, and Rector 2.6 warns about the duplicate (removes the entry; the orphaned import is left to Pint/Rector) doesNotUseIgnition()- Validates Ignition debugger is NOT installed
Local Development
phpVersionMatchesDdev()- Validates PHP version consistency with DDEV- π§
nodeVersion()- Validates the project pins Node >= 24 (the current LTS) in bothpackage.jsonengines.nodeand.nvmrc, compatible with each other (creates the missing constraint β establishing Node 24 when none is declared β and bumps a declaration that allows anything older to 24; a newer line such as Node 26 is left alone, and a conflict between existingengines.nodeand.nvmrcis reported, not auto-resolved) - π§
hardensNpmSupplyChain()- Hardens npm against supply-chain attacks: requirespackage.jsonengines.npm>= 12 (npm 12 blocks dependency lifecycle scripts by default and refuses git/remote deps),.npmrcengine-strict=trueso that requirement is enforced rather than advisory, and.npmrcmin-release-age=7for a 7-day install cooldown that skips freshly-published (potentially compromised) versions (setsengines.npmto^12, and upserts both.npmrckeys while preserving existing lines) - π§
ddevHasPcovPackage()- Validates DDEV coverage configuration (adds pcov to webimage_extra_packages and creates .ddev/php/90-custom.ini) ddevHasRedisAddon()- Validates DDEV Redis addon is installed and at minimum version 2.2.0- π§
ddevMutagenIgnoresNodeModules()- Validates DDEV Mutagen sync configuration (creates mutagen.yml and fixes .gitignore) - π§
ddevNodeVersionIsAuto()- Validates.ddev/config.yamlsetsnodejs_version: autoso DDEV derives the Node version from the project's.nvmrcinstead of pinning its own (setsnodejs_version: auto, preserving surrounding comments and formatting) updatesDdevAddons()- Fails if any installed DDEV add-on (.ddev/addon-metadata/*/manifest.yaml) has aninstall_dateolder than 3 months; comment shows theddev add-on get <repository>command to refresh each stale add-on
Build & Release
- π§
bumpsComposer()- Validates automatic composer dependency bumping (addscomposer bumpto post-update-cmd) - π§
releaseItBumpsWordpressThemeVersion()- Validates.release-it.jsonhas anafter:bumphook that rewrites theVersion:header ofstyle.css, so a release bumps the theme along with composer.json (wordpress profile, themes only; adds anode -eone-liner frompolicy/policy.jsonto the hooks) - π§
usesReleaseIt()- Validates automated release management (partial: creates/fixes .release-it.json and adds release npm script if packages installed) - π§
wordpressThemeVersionMatchesComposer()- Validates theVersion:header instyle.cssmatchescomposer.json'sversion, which@release-it/bumperkeeps current (wordpress profile, themes only; rewrites the header from composer.json, or seeds composer.json'sversionfrom the theme when it has none) hasNpmScripts()- Validates required npm build scripts- π§
biomeIgnoresCiArtifacts()- Validates thatbiome.json, when the project has one, keeps Biome away from the files a CI runner leaves in the project root βmetadata.json, which the GitLab runner extracts with the cache key whenever it restores a cache. Evaluatesfiles.includesthe way Biome does (in order, last match wins, noincludesmeaning every file), so an allowlist that never reaches the root passes. (appends"!metadata.json"to the end offiles.includesas a targeted text edit; a config withoutfiles.includesis only reported) - π§
biomeIgnoresLaravelLangFiles()- Validates thatbiome.json, when the project has one, keeps Biome away from Laravel's JSON translation files (lang/*.json,resources/lang/*.json), which translation tooling maintains and Biome's formatter would otherwise churn. Evaluatesfiles.includeslikebiomeIgnoresCiArtifacts(), probing each pattern withenand with every lang file the project has, so excluding only some locales still fails. (appends"!lang/*.json"/"!resources/lang/*.json"to the end offiles.includesas a targeted text edit; a config withoutfiles.includesis only reported) - π§
biomeUsesLocalSchema()- Validates thatbiome.json, when the project has one, points$schemaat./node_modules/@biomejs/biome/configuration_schema.jsonrather than a version-pinned remote URL, so the schema follows the installed Biome instead of needing a manual bump on every update. Passes when the project does not use Biome. (rewrites or inserts the$schemaline as a targeted text edit, leaving the rest of the file β comments included β byte-identical, since Biome formatsbiome.jsonitself)
Security & Configuration
- π§
modelShouldBeStrict()- ValidatesModel::shouldBeStrict()is called in AppServiceProvider withtrue, no argument, or a dynamic expression (notfalse) (addsModel::shouldBeStrict(! app()->isProduction())to boot()) - π§
formRequestFailOnUnknownFields()- ValidatesFormRequest::failOnUnknownFields()is called in AppServiceProvider (Laravel β₯13.6 only; warns on older versions) (addsFormRequest::failOnUnknownFields(! app()->isProduction())to boot()) hasDailyLogging()- Validates logging usesdailychannel (directly or viastack)hasEncryptedEnvFile()- Validates encrypted environment file existsusesReadableEncryptedEnvFile()- Validates the encrypted env file uses the readable line-per-variable format produced byddev artisan env:encrypt --readable(variable names stay visible in diffs), not the opaque blob format. Passes when no encrypted file exists (existence ishasEncryptedEnvFile's concern).doesNotHaveStrayEnvFiles()- Fails if a plaintext.env*file other than.env,.env.example,.env.testing,.env.viteor an encrypted.env.*.encryptedsits in the project root (allowlist inpolicy/policy.json). A gitignored.env.productionleft behind byenv:decryptis an unreviewed second copy of production secrets; re-encrypt any changes withddev artisan env:encrypt --readable, then delete it.doesNotPinOldMailTemplate()- Fails if a published mail view that pins the old template (resources/views/vendor/mail/html/themes/default.cssorhtml/header.blade.php) exists, preventing adoption of Laravel's modernized mail template.- π§
callsBaseline()- Validates self-validation runs after updates (adds/upgrades post-update-cmd entry to include--fix) - π§
doesNotExcludeUnknownChecks()- Fails whenconfig/baseline.phpexcludes a name no registered check answers to: excludes are matched against the registry, so an entry left behind by a check this package renamed or removed silences nothing and only hides that the exclusion is no longer in force (drops the dead entries, leaving the remaining excludes and the periodic state intact) doesNotCallPeriodicBaselineOnUpdate()- Fails ifphp artisan limenet:laravel-baseline:periodicis in thepost-update-cmdscripts (it shouldn't be β periodic checks fail CI automatically when expired)- π§
doesNotHaveGuidelinesScript()- Fails if the removedphp artisan limenet:laravel-baseline:guidelinescommand is still inpost-update-cmd(removed in v2.1.0) (removes the entry from composer.json) - π§
isInstalledAsRegularDependency()- Validateslimenet/laravel-baselineis inrequire(notrequire-dev), since a Laravel app loads it at runtime (moves from require-dev to require in composer.json) - π§
isInstalledAsDevDependency()- Validateslimenet/laravel-baselineis inrequire-dev(notrequire): outside Laravel it is only a development tool, so a--no-devdeploy leaves it out (php and wordpress profiles; moves from require to require-dev in composer.json) - π§
usesLaravelLang()- Validateslaravel-lang/langdev dependency is installed withlang:updateand pint in post-update scripts (partial: adds post-update scripts if package in require-dev)
Testing
composer test
Changelog
Please see CHANGELOG for more information on what has changed recently.
Contributing
Please see CONTRIBUTING for details.
Security Vulnerabilities
Please review our security policy on how to report security vulnerabilities.
Credits
License
The MIT License (MIT). Please see License File for more information.