@limenet-ch/baseline 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +272 -0
  3. package/js/dist/bin.d.ts +2 -0
  4. package/js/dist/bin.js +59 -0
  5. package/js/dist/bin.js.map +1 -0
  6. package/js/dist/checks/allows-tooling-in-claude-settings-check.d.ts +11 -0
  7. package/js/dist/checks/allows-tooling-in-claude-settings-check.js +14 -0
  8. package/js/dist/checks/allows-tooling-in-claude-settings-check.js.map +1 -0
  9. package/js/dist/checks/biome-uses-local-schema-check.d.ts +21 -0
  10. package/js/dist/checks/biome-uses-local-schema-check.js +78 -0
  11. package/js/dist/checks/biome-uses-local-schema-check.js.map +1 -0
  12. package/js/dist/checks/calls-baseline-check.d.ts +11 -0
  13. package/js/dist/checks/calls-baseline-check.js +34 -0
  14. package/js/dist/checks/calls-baseline-check.js.map +1 -0
  15. package/js/dist/checks/check.d.ts +37 -0
  16. package/js/dist/checks/check.js +54 -0
  17. package/js/dist/checks/check.js.map +1 -0
  18. package/js/dist/checks/ci-sets-node-version-check.d.ts +10 -0
  19. package/js/dist/checks/ci-sets-node-version-check.js +43 -0
  20. package/js/dist/checks/ci-sets-node-version-check.js.map +1 -0
  21. package/js/dist/checks/claude-settings-check.d.ts +30 -0
  22. package/js/dist/checks/claude-settings-check.js +43 -0
  23. package/js/dist/checks/claude-settings-check.js.map +1 -0
  24. package/js/dist/checks/denies-env-reads-in-claude-settings-check.d.ts +13 -0
  25. package/js/dist/checks/denies-env-reads-in-claude-settings-check.js +25 -0
  26. package/js/dist/checks/denies-env-reads-in-claude-settings-check.js.map +1 -0
  27. package/js/dist/checks/does-not-have-copilot-or-junie-agent-files-check.d.ts +5 -0
  28. package/js/dist/checks/does-not-have-copilot-or-junie-agent-files-check.js +25 -0
  29. package/js/dist/checks/does-not-have-copilot-or-junie-agent-files-check.js.map +1 -0
  30. package/js/dist/checks/does-not-use-both-baseline-runners-check.d.ts +13 -0
  31. package/js/dist/checks/does-not-use-both-baseline-runners-check.js +30 -0
  32. package/js/dist/checks/does-not-use-both-baseline-runners-check.js.map +1 -0
  33. package/js/dist/checks/hardens-npm-supply-chain-check.d.ts +5 -0
  34. package/js/dist/checks/hardens-npm-supply-chain-check.js +68 -0
  35. package/js/dist/checks/hardens-npm-supply-chain-check.js.map +1 -0
  36. package/js/dist/checks/has-ci-jobs-check.d.ts +9 -0
  37. package/js/dist/checks/has-ci-jobs-check.js +14 -0
  38. package/js/dist/checks/has-ci-jobs-check.js.map +1 -0
  39. package/js/dist/checks/has-editorconfig-check.d.ts +6 -0
  40. package/js/dist/checks/has-editorconfig-check.js +42 -0
  41. package/js/dist/checks/has-editorconfig-check.js.map +1 -0
  42. package/js/dist/checks/has-npm-scripts-check.d.ts +5 -0
  43. package/js/dist/checks/has-npm-scripts-check.js +20 -0
  44. package/js/dist/checks/has-npm-scripts-check.js.map +1 -0
  45. package/js/dist/checks/has-trivy-config-check.d.ts +26 -0
  46. package/js/dist/checks/has-trivy-config-check.js +179 -0
  47. package/js/dist/checks/has-trivy-config-check.js.map +1 -0
  48. package/js/dist/checks/is-ci-lint-complete-check.d.ts +10 -0
  49. package/js/dist/checks/is-ci-lint-complete-check.js +25 -0
  50. package/js/dist/checks/is-ci-lint-complete-check.js.map +1 -0
  51. package/js/dist/checks/node-version-check.d.ts +5 -0
  52. package/js/dist/checks/node-version-check.js +77 -0
  53. package/js/dist/checks/node-version-check.js.map +1 -0
  54. package/js/dist/checks/periodic-check.d.ts +16 -0
  55. package/js/dist/checks/periodic-check.js +34 -0
  56. package/js/dist/checks/periodic-check.js.map +1 -0
  57. package/js/dist/checks/registry.d.ts +10 -0
  58. package/js/dist/checks/registry.js +56 -0
  59. package/js/dist/checks/registry.js.map +1 -0
  60. package/js/dist/checks/runs-ci-lint-hook-in-claude-settings-check.d.ts +6 -0
  61. package/js/dist/checks/runs-ci-lint-hook-in-claude-settings-check.js +26 -0
  62. package/js/dist/checks/runs-ci-lint-hook-in-claude-settings-check.js.map +1 -0
  63. package/js/dist/checks/updates-dependencies-check.d.ts +5 -0
  64. package/js/dist/checks/updates-dependencies-check.js +8 -0
  65. package/js/dist/checks/updates-dependencies-check.js.map +1 -0
  66. package/js/dist/checks/uses-release-it-check.d.ts +12 -0
  67. package/js/dist/checks/uses-release-it-check.js +40 -0
  68. package/js/dist/checks/uses-release-it-check.js.map +1 -0
  69. package/js/dist/commands/check.d.ts +7 -0
  70. package/js/dist/commands/check.js +59 -0
  71. package/js/dist/commands/check.js.map +1 -0
  72. package/js/dist/commands/install-skills.d.ts +16 -0
  73. package/js/dist/commands/install-skills.js +47 -0
  74. package/js/dist/commands/install-skills.js.map +1 -0
  75. package/js/dist/commands/periodic.d.ts +6 -0
  76. package/js/dist/commands/periodic.js +40 -0
  77. package/js/dist/commands/periodic.js.map +1 -0
  78. package/js/dist/index.d.ts +8 -0
  79. package/js/dist/index.js +9 -0
  80. package/js/dist/index.js.map +1 -0
  81. package/js/dist/policy.d.ts +45 -0
  82. package/js/dist/policy.js +145 -0
  83. package/js/dist/policy.js.map +1 -0
  84. package/js/dist/project.d.ts +26 -0
  85. package/js/dist/project.js +64 -0
  86. package/js/dist/project.js.map +1 -0
  87. package/js/dist/state.d.ts +21 -0
  88. package/js/dist/state.js +36 -0
  89. package/js/dist/state.js.map +1 -0
  90. package/js/dist/support/ci-jobs.d.ts +13 -0
  91. package/js/dist/support/ci-jobs.js +42 -0
  92. package/js/dist/support/ci-jobs.js.map +1 -0
  93. package/js/dist/support/npmrc.d.ts +10 -0
  94. package/js/dist/support/npmrc.js +36 -0
  95. package/js/dist/support/npmrc.js.map +1 -0
  96. package/js/dist/support/versions.d.ts +10 -0
  97. package/js/dist/support/versions.js +34 -0
  98. package/js/dist/support/versions.js.map +1 -0
  99. package/js/skills/creating-a-release/SKILL.md +45 -0
  100. package/js/skills/updating-dependencies/SKILL.md +104 -0
  101. package/package.json +67 -0
  102. package/policy/policy.json +131 -0
  103. package/policy/policy.schema.json +284 -0
  104. package/policy/templates/editorconfig +18 -0
  105. package/policy/templates/trivy.yaml +19 -0
package/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) Linus Metzler <hi@linusmetzler.me>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,272 @@
1
+ # Laravel Baseline
2
+
3
+ [![Latest Version on Packagist](https://img.shields.io/packagist/v/limenet/laravel-baseline.svg?style=flat-square)](https://packagist.org/packages/limenet/laravel-baseline)
4
+ [![run-tests](https://github.com/limenet/laravel-baseline/actions/workflows/run-tests.yml/badge.svg)](https://github.com/limenet/laravel-baseline/actions/workflows/run-tests.yml)
5
+ [![Fix PHP code style issues](https://github.com/limenet/laravel-baseline/actions/workflows/fix-php-code-style-issues.yml/badge.svg)](https://github.com/limenet/laravel-baseline/actions/workflows/fix-php-code-style-issues.yml)
6
+ [![codecov](https://codecov.io/gh/limenet/laravel-baseline/graph/badge.svg?token=Q57FG1L28A)](https://codecov.io/gh/limenet/laravel-baseline)
7
+ [![Total Downloads](https://img.shields.io/packagist/dt/limenet/laravel-baseline.svg?style=flat-square)](https://packagist.org/packages/limenet/laravel-baseline)
8
+
9
+ Checks your Laravel installation against a highly opinionated baseline.
10
+
11
+ This repository ships **two runners** from one policy:
12
+
13
+ | | Composer | npm |
14
+ | --- | --- | --- |
15
+ | Package | `limenet/laravel-baseline` | `@limenet-ch/baseline` |
16
+ | For | Laravel projects (DDEV, composer) | JS/TS-only projects (no PHP, no DDEV) |
17
+ | Command | `php artisan limenet:laravel-baseline:check` | `npx baseline check` |
18
+ | Checks | all of them | [the portable subset](#js-only-projects) |
19
+
20
+ Both read `policy/`, so the version floors and required keys are defined once, and both are
21
+ executed against the shared behavioural fixtures in `fixtures/`. They are released in lockstep:
22
+ the same version number means the same policy in both ecosystems.
23
+
24
+
25
+ ## Installation
26
+
27
+ You can install the package via composer:
28
+
29
+ ```bash
30
+ composer require limenet/laravel-baseline
31
+ ```
32
+
33
+
34
+ You can publish the config file with:
35
+
36
+ ```bash
37
+ php artisan vendor:publish --tag="laravel-baseline-config"
38
+ ```
39
+
40
+ ## Usage
41
+
42
+ Add to your `composer.json` to run checks (and auto-fix) after every `composer update`:
43
+
44
+ ```json
45
+ "post-update-cmd": [
46
+ "@php artisan limenet:laravel-baseline:check --fix"
47
+ ],
48
+ ```
49
+
50
+ ### Running checks
51
+
52
+ ```bash
53
+ # Check only — report issues without making changes
54
+ php artisan limenet:laravel-baseline:check
55
+
56
+ # Auto-fix — apply all safe automatic fixes, then report remaining issues
57
+ php artisan limenet:laravel-baseline:check --fix
58
+ ```
59
+
60
+ **Checks marked 🔧** below support `--fix`. When `--fix` is used:
61
+ - Fully fixable checks: all conditions are applied automatically.
62
+ - Partially fixable checks *(requires package installed first)*: configuration/script entries are fixed once the package is installed via `composer require`.
63
+ - Non-fixable checks: report the issue with an actionable message.
64
+
65
+ ### AI guidelines & skills
66
+
67
+ The package also ships [Laravel Boost](https://laravel.com/docs/boost) resources under
68
+ `resources/boost/`: an always-on guideline (the dev loop — `ci-lint`, tests, DDEV-first
69
+ conventions) and on-demand skills (e.g. `creating-a-release`). When a project that has
70
+ `laravel/boost` installed runs `php artisan boost:install` or `php artisan boost:update --discover`,
71
+ Boost discovers and publishes these to the consuming project's coding agents automatically.
72
+
73
+ ## JS-only projects
74
+
75
+ Projects with no PHP and no DDEV use the npm runner instead. It is a second implementation, not a
76
+ wrapper: PHP cannot reach into a JS project (and by this baseline's own convention `npm` runs on
77
+ the host while artisan runs inside DDEV), so the portable checks are reimplemented in TypeScript and
78
+ kept honest by the shared fixtures rather than by shared code.
79
+
80
+ ```bash
81
+ npm install --save-dev @limenet-ch/baseline
82
+ ```
83
+
84
+ ```bash
85
+ npx baseline check # report issues
86
+ npx baseline check --fix # apply safe fixes, then report what is left
87
+ npx baseline periodic # walk through expired periodic checks
88
+ npx baseline install-skills # copy the packaged skills into .claude/skills/
89
+ ```
90
+
91
+ Wire it into the `ci-lint` npm script so CI and the Claude Stop hook both run it — npm has no
92
+ `post-update-cmd` equivalent, and npm 12 blocks dependency lifecycle scripts by default:
93
+
94
+ ```json
95
+ "scripts": {
96
+ "ci-lint": "biome ci . && tsc --noEmit && baseline check"
97
+ }
98
+ ```
99
+
100
+ State lives in `.baseline.json` at the project root (a JS project has no `config/` directory):
101
+
102
+ ```json
103
+ {
104
+ "excludes": ["hasNpmScripts"],
105
+ "periodic": { "updatesDependencies": "2026-08-16T09:00:00.000Z" }
106
+ }
107
+ ```
108
+
109
+ ### What the npm runner checks
110
+
111
+ | Check | Relationship to the Laravel runner |
112
+ | --- | --- |
113
+ | `nodeVersion` | identical |
114
+ | `hardensNpmSupplyChain` | identical |
115
+ | `hasEditorconfig` | identical |
116
+ | `biomeUsesLocalSchema` | identical |
117
+ | `doesNotHaveCopilotOrJunieAgentFiles` | identical |
118
+ | `doesNotUseBothBaselineRunners` | mirrored: fails when `composer.json` requires `limenet/laravel-baseline`, since the Composer runner wins |
119
+ | `allowsToolingInClaudeSettings` | requires only the shared allow entries, not the DDEV/artisan ones |
120
+ | `deniesEnvReadsInClaudeSettings` | identical |
121
+ | `runsCiLintHookInClaudeSettings` | hooks `npm run ci-lint` instead of `ddev composer run ci-lint` |
122
+ | `updatesDependencies` | identical (periodic, every 30 days) |
123
+ | `hasNpmScripts` | identical |
124
+ | `hasCiJobs` | same GitLab CI templates, without the `php` job |
125
+ | `hasTrivyConfig` | identical, canonical config included: its `vendor/**`, `storage/logs/` and `.ddev/` skips are inert in a JS project |
126
+ | `ciSetsNodeVersion` | identical |
127
+ | `isCiLintComplete` | asserts the JS toolchain in the npm script, not pint/phpstan in a composer script |
128
+ | `callsBaseline` | hooks the `ci-lint` npm script, since npm has no `post-update-cmd` |
129
+ | `usesReleaseIt` | **inverted**: fails if `@release-it/bumper` is configured, because `package.json` is already release-it's source of truth |
130
+
131
+ Deliberately not ported: everything composer-, artisan-, Rector-, PHPStan- or Spatie-Health-shaped;
132
+ the DDEV checks (`ddevNodeVersionIsAuto`, `ddevMutagenIgnoresNodeModules`, …); and
133
+ `hasClaudeSettingsWithLaravelSkills` / `doesNotHaveLaravelSimplifierInClaudeSettings`, which are
134
+ vacuous without Laravel.
135
+
136
+ ## Checks
137
+
138
+ This package validates your Laravel installation against the following checks:
139
+
140
+ ### Testing & Quality Tools
141
+ - **`usesPest()`** - Validates Pest testing framework is configured (not PHPUnit directly)
142
+ - **`usesPestPhpstanPlugin()`** - Validates `pestphp/pest-plugin-phpstan` is installed when Pest 5+ and PHPStan are both present (warns if not applicable)
143
+ - **`usesPestRectorPlugin()`** - Validates `pestphp/pest-plugin-rector` is installed when Pest 5+ and Rector are both present (warns if not applicable)
144
+ - 🔧 **`usesRector()`** - Validates Rector automated code modernization is installed *(partial: fixes ci-lint script if packages installed)*
145
+ - **`usesLarastan()`** - Validates Larastan static analysis tool is configured
146
+ - **`usesPhpstanExtensions()`** - Validates PHPStan extensions are installed
147
+ - **`phpstanLevelAtLeastEight()`** - Validates PHPStan is configured to at least level 8
148
+ - 🔧 **`checkPhpunit()`** - Validates PHPUnit configuration with coverage reports *(adds missing XML nodes and APP_KEY)*
149
+ - 🔧 **`hasRectorConfigWithComposerBased()`** - Validates Rector `withComposerBased(phpunit, symfony, laravel)` is configured *(appends call to rector.php)*
150
+ - 🔧 **`hasRectorConfigWithConfiguredRules()`** - Validates Rector `withConfiguredRule()` calls are present for `RouteActionCallableRector` and `WhereToWhereLikeRector` *(appends calls to rector.php)*
151
+ - 🔧 **`hasRectorConfigWithPreparedSets()`** - Validates Rector `withPreparedSets(deadCode, codeQuality, codingStyle, typeDeclarations, privatization, instanceOf, earlyReturn)` is configured *(appends call to rector.php)*
152
+ - 🔧 **`hasRectorConfigWithImportNames()`** - Validates Rector `withImportNames(importShortClasses: false)` is configured *(appends call to rector.php)*
153
+ - 🔧 **`hasRectorConfigWithPhpSets()`** - Validates Rector `withPhpSets()` is called *(appends call to rector.php)*
154
+ - 🔧 **`hasRectorConfigWithAttributesSets()`** - Validates Rector `withAttributesSets()` is called *(appends call to rector.php)*
155
+ - 🔧 **`hasRectorConfigWithSetProviders()`** - Validates Rector `withSetProviders(LaravelSetProvider)` is configured *(appends call to rector.php)*
156
+ - 🔧 **`hasRectorConfigWithRules()`** - Validates Rector `withRules([AddGenericReturnTypeToRelationsRector, MinutesToSecondsInCacheRector, UseForwardsCallsTraitRector])` is configured *(appends call to rector.php)*
157
+ - 🔧 **`hasRectorConfigWithSets()`** - Validates Rector `withSets([LaravelBaselineSetList::REMOVE_DEFAULT_DOCBLOCKS, LaravelSetList::LARAVEL_*])` is configured with all required sets *(appends call to rector.php)*
158
+ - 🔧 **`hasRectorConfigWithPaths()`** - Validates Rector `withPaths([app, database, routes, tests])` is configured *(appends call to rector.php)*
159
+ - 🔧 **`hasRectorConfigWithPestSet()`** - Validates Rector `withSets([PestSetList::CODING_STYLE])` is configured when Pest 5+ and Rector are both present *(appends call to rector.php; warns if not applicable)*
160
+ - 🔧 **`hasRectorConfigWithSkip()`** - Validates Rector `withSkip()` contains required skipped rules (always: 6 Laravel rules; Laravel 13+: TablePropertyToTableAttributeRector; when server.php exists: ServerVariableToRequestFacadeRector) *(appends call to rector.php)*
161
+
162
+ ### IDE & Developer Tools
163
+ - 🔧 **`hasEditorconfig()`** - Validates `.editorconfig` exists with required settings (`root = true`, `charset`, `end_of_line`, `indent_style`, `insert_final_newline`, `trim_trailing_whitespace`) *(creates `.editorconfig` with canonical content if missing or incomplete)*
164
+ - 🔧 **`hasClaudeSettingsWithLaravelSkills()`** - Validates Claude Code settings include the Laravel agent skills plugin and marketplace *(creates/merges `.claude/settings.json`)*
165
+ - 🔧 **`doesNotHaveLaravelSimplifierInClaudeSettings()`** - Fails if the deprecated `laravel-simplifier@laravel` plugin is still enabled in `.claude/settings.json` *(removes the entry)*
166
+ - 🔧 **`deniesEnvReadsInClaudeSettings()`** - Validates `.claude/settings.json` `permissions.deny` blocks reading `.env` plus every environment that ships an encrypted file (each `.env.{env}.encrypted` in the project root requires denying `.env.{env}`); `.env.example` stays readable *(merges the deny entries)*
167
+ - 🔧 **`allowsToolingInClaudeSettings()`** - Validates `.claude/settings.json` `permissions.allow` includes 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)*
168
+ - 🔧 **`asksBeforeDestructiveDbCommandsInClaudeSettings()`** - Validates `.claude/settings.json` `permissions.ask` requires a confirmation before the artisan commands that destroy database contents (`migrate:fresh`, `migrate:refresh`, `migrate:reset`, `migrate:rollback`, `db:wipe`), covering both the `ddev artisan` and `php artisan` forms; `ask` rather than `deny` so the developer keeps an approval path *(merges the ask entries)*
169
+ - 🔧 **`runsCiLintHookInClaudeSettings()`** - Validates `.claude/settings.json` has a `Stop` hook running `ddev composer run ci-lint` *(appends the hook)*
170
+ - 🔧 **`usesIdeHelpers()`** - Validates Laravel IDE Helper is configured: `post-update-cmd` runs `ide-helper:generate`, `ide-helper:models`, and `ide-helper:meta`, and `.gitignore` ignores the generated `_ide_helper.php`, `_ide_helper_models.php`, and `.phpstorm.meta.php` files *(partial: adds post-update scripts and gitignore entries if package installed)*
171
+ - 🔧 **`gitignoresLspFiles()`** - Validates `.gitignore` ignores `storage/framework/lsp-*.php`, the per-editor files the Laravel language server writes into `storage/framework` *(appends the entry, creating `.gitignore` if missing)*
172
+ - **`usesLaravelAdminer()`** - Warns if Laravel Adminer database UI is missing (optional), validates TFA confirmation and configuration when installed
173
+ - 🔧 **`usesLaravelBoost()`** - Validates Laravel Boost AI development tool *(partial: fixes boost.json and post-update script if package installed)*
174
+ - 🔧 **`laravelBoostMcpUsesDdev()`** - Validates `.mcp.json` configures the `laravel-boost` MCP server to use `ddev artisan boost:mcp` *(creates/fixes `.mcp.json`; warns if `laravel/boost` not installed)*
175
+ - 🔧 **`doesNotHaveCopilotOrJunieAgentFiles()`** - Fails if `AGENTS.md`, a `.junie` directory, or a `.github/skills` directory exist — these are generated for the Copilot/Junie Boost agents, which are no longer required *(deletes `AGENTS.md`, the `.junie` directory, and the `.github/skills` directory)*
176
+ - **`runsBoostUpdate()`** *(periodic, every 30 days)* - Warns if Laravel Boost is not installed; when installed, fails until a developer confirms running `php artisan boost:update --discover` via `limenet:laravel-baseline:periodic`
177
+ - **`followsModernLaravelIdioms()`** *(periodic, every 30 days)* - Fails until a developer confirms running the `auditing-laravel-idioms` skill, which audits typed cache getters, BackedEnum cache/session keys, and `whenFilledEnum()` for request data
178
+ - **`updatesDependencies()`** *(periodic, every 30 days)* - Fails until a developer confirms (via `limenet:laravel-baseline:periodic`) that composer & npm dependencies were updated by running the `updating-dependencies` skill — which updates in-constraint packages, reviews changelogs for project impact, and reports semver-blocked majors
179
+ - 🔧 **`usesLimenetPintConfig()`** - Validates custom Laravel Pint formatting standards *(partial: adds post-update script if package installed)*
180
+
181
+ ### Laravel Features & Monitoring
182
+ - 🔧 **`usesLaravelHorizon()`** - Validates Laravel Horizon Redis queue manager *(partial: adds ci-deploy-post script if package installed)*
183
+ - **`usesLaravelPennant()`** - Warns if Laravel Pennant feature flags are missing (optional)
184
+ - 🔧 **`usesLaravelPulse()`** - Validates Laravel Pulse application monitoring *(partial: adds PULSE_ENABLED=false to phpunit.xml if package installed)*
185
+ - 🔧 **`cacheAllowsPulseSerializableClasses()`** - On Laravel 13+ with Pulse installed, validates the top-level `serializable_classes` allow-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 is `false` or an incomplete array)*
186
+ - 🔧 **`usesLaravelTelescope()`** - Validates Laravel Telescope request debugging *(partial: adds post-update script and TELESCOPE_ENABLED=false to phpunit.xml if package installed)*
187
+ - **`usesSpatieHealthSetup()`** - Validates Spatie Health packages, schedules, s3_health disk, and result store configuration
188
+ - 🔧 **`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)*
189
+ - 🔧 **`usesSpatieHealthHasLaravelVersionCheck()`** - Validates LaravelVersionCheck is registered in Health::checks() *(adds to AppServiceProvider)*
190
+ - 🔧 **`usesSpatieHealthHasPhpVersionCheck()`** - Validates PhpVersionCheck is registered in Health::checks() *(adds to AppServiceProvider)*
191
+ - 🔧 **`usesSpatieHealthHasReleaseAgeCheck()`** - Validates ReleaseAgeCheck is registered in Health::checks() *(adds to AppServiceProvider)*
192
+ - **`usesSpatieHealthCacheCheckCacheStore()`** - Validates CacheCheck uses the dedicated 'health-checks' cache store via `->driver('health-checks')` in AppServiceProvider and config/cache.php
193
+ - **`usesSpatieHealthQueueCheckCacheStore()`** - Validates QueueCheck: DispatchQueueCheckJobsCommand is scheduled everyMinute(), uses the dedicated 'health-checks' file cache store in AppServiceProvider and config/cache.php
194
+ - **`usesSpatieHealthQueueCheckHorizonQueues()`** - Validates QueueCheck registers all queues from config/horizon.php via onQueue() (requires laravel/horizon)
195
+ - **`usesSpatieHealthScheduleCheckCacheStore()`** - Validates ScheduleCheck uses the dedicated 'health-checks' cache store in both AppServiceProvider and config/cache.php
196
+ - 🔧 **`usesSpatieHealthScheduleCheckHeartbeat()`** - Validates ScheduleCheck is configured with `heartbeatMaxAgeInMinutes(2)` to prevent false positives *(appends the call to ScheduleCheck in AppServiceProvider)*
197
+ - **`usesSpatieBackup()`** - Validates Spatie Backup database backups with comprehensive config validation
198
+
199
+ ### Infrastructure & Dependencies
200
+ - **`usesPredis()`** - Validates Predis Redis client is installed
201
+ - **`isLaravelVersionMaintained()`** - Validates Laravel 11+ is used
202
+ - 🔧 **`doesNotUseSail()`** - Validates Sail is NOT used *(partial: deletes docker-compose.yml; run `composer remove laravel/sail` manually)*
203
+ - 🔧 **`doesNotUsePhpInsights()`** - Validates PHP Insights is NOT used *(removes the `nunomaduro/phpinsights` composer.json entry, leftover ci-lint script entries, and config/insights.php; run `composer update` afterward to sync composer.lock)*
204
+ - **`doesNotUseSpatiePasskeysWithFortify()`** - Fails if both `spatie/laravel-passkeys` and `laravel/fortify` are installed, as they overlap in authentication responsibility
205
+ - **`doesNotUseBothBaselineRunners()`** - Fails when `package.json` also 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 the `npm uninstall` to run; never uninstalls for you)
206
+ - **`doesNotUseHorizonWatcher()`** - Validates Spatie Horizon Watcher is NOT installed
207
+ - 🔧 **`doesNotUseGreaterThanOrEqualConstraints()`** - Validates no `>=` version constraints in composer.json (use `^` or `~` instead) *(replaces `>=X.Y` with `^X.Y` in composer.json)*
208
+
209
+ ### CI/CD & Deployment
210
+ - **`hasCiJobs()`** - Validates GitLab CI pipeline jobs are properly configured (the `test` job may extend either `.test` or `.test_db`)
211
+ - 🔧 **`hasTrivyConfig()`** - Validates Trivy security scanning CI job, `trivy.yaml` (scanners, skip-files, skip-dirs, ignorefile, cache.dir, telemetry/VEX/dependency-tree flags, and `pkg.include-dev-deps` so 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)*
212
+ - **`callsSentryHook()`** - Warns if Sentry error tracking is missing (optional)
213
+ - **`phpVersionMatchesCi()`** - Validates PHP version consistency with CI configuration
214
+ - **`isCiLintComplete()`** - Validates complete linting pipeline
215
+ - 🔧 **`ciSetsNodeVersion()`** - Validates `.gitlab-ci.yml` sets `variables.NODE_VERSION` to `latest`, so the shared CI template resolves the Node version instead of drifting from the project *(adds the variable, creating the `variables` mapping if needed, preserving comments and existing entries)*
216
+ - **`doesNotUseIgnition()`** - Validates Ignition debugger is NOT installed
217
+
218
+ ### Local Development
219
+ - **`phpVersionMatchesDdev()`** - Validates PHP version consistency with DDEV
220
+ - 🔧 **`nodeVersion()`** - Validates the project pins Node >= 24 (the current LTS) in both `package.json` `engines.node` and `.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 existing `engines.node` and `.nvmrc` is reported, not auto-resolved)*
221
+ - 🔧 **`hardensNpmSupplyChain()`** - Hardens npm against supply-chain attacks: requires `package.json` `engines.npm` >= 12 (npm 12 blocks dependency lifecycle scripts by default and refuses git/remote deps), `.npmrc` `engine-strict=true` so that requirement is enforced rather than advisory, and `.npmrc` `min-release-age=7` for a 7-day install cooldown that skips freshly-published (potentially compromised) versions *(sets `engines.npm` to `^12`, and upserts both `.npmrc` keys while preserving existing lines)*
222
+ - 🔧 **`ddevHasPcovPackage()`** - Validates DDEV coverage configuration *(adds pcov to webimage_extra_packages and creates .ddev/php/90-custom.ini)*
223
+ - **`ddevHasRedisAddon()`** - Validates DDEV Redis addon is installed and at minimum version 2.2.0
224
+ - 🔧 **`ddevMutagenIgnoresNodeModules()`** - Validates DDEV Mutagen sync configuration *(creates mutagen.yml and fixes .gitignore)*
225
+ - 🔧 **`ddevNodeVersionIsAuto()`** - Validates `.ddev/config.yaml` sets `nodejs_version: auto` so DDEV derives the Node version from the project's `.nvmrc` instead of pinning its own *(sets `nodejs_version: auto`, preserving surrounding comments and formatting)*
226
+ - **`updatesDdevAddons()`** - Fails if any installed DDEV add-on (`.ddev/addon-metadata/*/manifest.yaml`) has an `install_date` older than 3 months; comment shows the `ddev add-on get <repository>` command to refresh each stale add-on
227
+
228
+ ### Build & Release
229
+ - 🔧 **`bumpsComposer()`** - Validates automatic composer dependency bumping *(adds `composer bump` to post-update-cmd)*
230
+ - 🔧 **`usesReleaseIt()`** - Validates automated release management *(partial: creates/fixes .release-it.json and adds release npm script if packages installed)*
231
+ - **`hasNpmScripts()`** - Validates required npm build scripts
232
+ - 🔧 **`biomeUsesLocalSchema()`** - Validates that `biome.json`, when the project has one, points `$schema` at `./node_modules/@biomejs/biome/configuration_schema.json` rather 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 `$schema` line as a targeted text edit, leaving the rest of the file — comments included — byte-identical, since Biome formats `biome.json` itself)*
233
+
234
+ ### Security & Configuration
235
+ - 🔧 **`modelShouldBeStrict()`** - Validates `Model::shouldBeStrict()` is called in AppServiceProvider with `true`, no argument, or a dynamic expression (not `false`) *(adds `Model::shouldBeStrict(! app()->isProduction())` to boot())*
236
+ - 🔧 **`formRequestFailOnUnknownFields()`** - Validates `FormRequest::failOnUnknownFields()` is called in AppServiceProvider (Laravel ≥13.6 only; warns on older versions) *(adds `FormRequest::failOnUnknownFields(! app()->isProduction())` to boot())*
237
+ - **`hasDailyLogging()`** - Validates logging uses `daily` channel (directly or via `stack`)
238
+ - **`hasEncryptedEnvFile()`** - Validates encrypted environment file exists
239
+ - **`usesReadableEncryptedEnvFile()`** - Validates the encrypted env file uses the readable line-per-variable format produced by `ddev artisan env:encrypt --readable` (variable names stay visible in diffs), not the opaque blob format. Passes when no encrypted file exists (existence is `hasEncryptedEnvFile`'s concern).
240
+ - **`doesNotPinOldMailTemplate()`** - Fails if a published mail view that pins the old template (`resources/views/vendor/mail/html/themes/default.css` or `html/header.blade.php`) exists, preventing adoption of Laravel's modernized mail template.
241
+ - 🔧 **`callsBaseline()`** - Validates self-validation runs after updates *(adds/upgrades post-update-cmd entry to include `--fix`)*
242
+ - **`doesNotCallPeriodicBaselineOnUpdate()`** - Fails if `php artisan limenet:laravel-baseline:periodic` is in the `post-update-cmd` scripts (it shouldn't be — periodic checks fail CI automatically when expired)
243
+ - 🔧 **`doesNotHaveGuidelinesScript()`** - Fails if the removed `php artisan limenet:laravel-baseline:guidelines` command is still in `post-update-cmd` (removed in v2.1.0) *(removes the entry from composer.json)*
244
+ - 🔧 **`isInstalledAsRegularDependency()`** - Validates `limenet/laravel-baseline` is in `require` (not `require-dev`) *(moves from require-dev to require in composer.json)*
245
+ - 🔧 **`usesLaravelLang()`** - Validates `laravel-lang/lang` dev dependency is installed with `lang:update` and pint in post-update scripts *(partial: adds post-update scripts if package in require-dev)*
246
+
247
+ ## Testing
248
+
249
+ ```bash
250
+ composer test
251
+ ```
252
+
253
+ ## Changelog
254
+
255
+ Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.
256
+
257
+ ## Contributing
258
+
259
+ Please see [CONTRIBUTING](CONTRIBUTING.md) for details.
260
+
261
+ ## Security Vulnerabilities
262
+
263
+ Please review [our security policy](../../security/policy) on how to report security vulnerabilities.
264
+
265
+ ## Credits
266
+
267
+ - [Linus Metzler](https://github.com/limenet)
268
+ - [All Contributors](../../contributors)
269
+
270
+ ## License
271
+
272
+ The MIT License (MIT). Please see [License File](LICENSE.md) for more information.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/js/dist/bin.js ADDED
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env node
2
+ import { parseArgs } from 'node:util';
3
+ import { runCheck } from './commands/check.js';
4
+ import { runInstallSkills } from './commands/install-skills.js';
5
+ import { runPeriodic } from './commands/periodic.js';
6
+ import { Project } from './project.js';
7
+ const USAGE = `baseline — a highly opinionated baseline for JS/TS projects.
8
+
9
+ Usage:
10
+ baseline check [--fix] [--verbose] [--cwd <path>]
11
+ baseline periodic [--cwd <path>]
12
+ baseline install-skills [--force] [--cwd <path>]
13
+
14
+ Options:
15
+ --fix Repair what can be repaired, then re-verify
16
+ --verbose Show passing checks too
17
+ --force Overwrite skills that are already installed
18
+ --cwd Project root to inspect (default: the current directory)
19
+ --help Show this message
20
+ `;
21
+ async function main() {
22
+ const { values, positionals } = parseArgs({
23
+ allowPositionals: true,
24
+ options: {
25
+ fix: { type: 'boolean', default: false },
26
+ verbose: { type: 'boolean', short: 'v', default: false },
27
+ force: { type: 'boolean', default: false },
28
+ cwd: { type: 'string' },
29
+ help: { type: 'boolean', short: 'h', default: false },
30
+ },
31
+ });
32
+ const command = positionals[0];
33
+ if (values.help || command === undefined) {
34
+ console.log(USAGE);
35
+ return values.help ? 0 : 1;
36
+ }
37
+ const project = Project.at(values.cwd);
38
+ switch (command) {
39
+ case 'check':
40
+ return runCheck(project, { fix: values.fix, verbose: values.verbose });
41
+ case 'periodic':
42
+ return await runPeriodic(project);
43
+ case 'install-skills':
44
+ return runInstallSkills(project, { force: values.force });
45
+ default:
46
+ console.error(`Unknown command "${command}".\n\n${USAGE}`);
47
+ return 1;
48
+ }
49
+ }
50
+ try {
51
+ process.exitCode = await main();
52
+ }
53
+ catch (error) {
54
+ // Unreadable policy, malformed JSON in the project: actionable on its own, so
55
+ // it is reported as a message rather than as a stack trace.
56
+ console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
57
+ process.exitCode = 1;
58
+ }
59
+ //# sourceMappingURL=bin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AAC9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAA;AAC/D,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAA;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAEtC,MAAM,KAAK,GAAG;;;;;;;;;;;;;CAab,CAAA;AAED,KAAK,UAAU,IAAI;IACf,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,GAAG,SAAS,CAAC;QACtC,gBAAgB,EAAE,IAAI;QACtB,OAAO,EAAE;YACL,GAAG,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE;YACxC,OAAO,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE;YACxD,KAAK,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE;YAC1C,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;YACvB,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE;SACxD;KACJ,CAAC,CAAA;IAEF,MAAM,OAAO,GAAG,WAAW,CAAC,CAAC,CAAC,CAAA;IAE9B,IAAI,MAAM,CAAC,IAAI,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QACvC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;QAElB,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IAC9B,CAAC;IAED,MAAM,OAAO,GAAG,OAAO,CAAC,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;IAEtC,QAAQ,OAAO,EAAE,CAAC;QACd,KAAK,OAAO;YACR,OAAO,QAAQ,CAAC,OAAO,EAAE,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,CAAA;QAC1E,KAAK,UAAU;YACX,OAAO,MAAM,WAAW,CAAC,OAAO,CAAC,CAAA;QACrC,KAAK,gBAAgB;YACjB,OAAO,gBAAgB,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAA;QAC7D;YACI,OAAO,CAAC,KAAK,CAAC,oBAAoB,OAAO,SAAS,KAAK,EAAE,CAAC,CAAA;YAE1D,OAAO,CAAC,CAAA;IAChB,CAAC;AACL,CAAC;AAED,IAAI,CAAC;IACD,OAAO,CAAC,QAAQ,GAAG,MAAM,IAAI,EAAE,CAAA;AACnC,CAAC;AAAC,OAAO,KAAK,EAAE,CAAC;IACb,8EAA8E;IAC9E,4DAA4D;IAC5D,OAAO,CAAC,KAAK,CAAC,UAAU,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;IAEjF,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAA;AACxB,CAAC"}
@@ -0,0 +1,11 @@
1
+ import type { CheckResult } from './check.js';
2
+ import { ClaudeSettingsCheck } from './claude-settings-check.js';
3
+ /**
4
+ * Only the shared half of the policy's allow list applies here — the DDEV and
5
+ * artisan entries the PHP runner also requires are unsatisfiable in a project
6
+ * with neither.
7
+ */
8
+ export declare class AllowsToolingInClaudeSettingsCheck extends ClaudeSettingsCheck {
9
+ static readonly checkName = "allowsToolingInClaudeSettings";
10
+ fix(dry?: boolean): CheckResult;
11
+ }
@@ -0,0 +1,14 @@
1
+ import { policy } from '../policy.js';
2
+ import { ClaudeSettingsCheck } from './claude-settings-check.js';
3
+ /**
4
+ * Only the shared half of the policy's allow list applies here — the DDEV and
5
+ * artisan entries the PHP runner also requires are unsatisfiable in a project
6
+ * with neither.
7
+ */
8
+ export class AllowsToolingInClaudeSettingsCheck extends ClaudeSettingsCheck {
9
+ static checkName = 'allowsToolingInClaudeSettings';
10
+ fix(dry = false) {
11
+ return this.requireEntries('allow', policy().strings('claude.allow.shared'), (entry) => `Claude settings: add "${entry}" to permissions.allow in .claude/settings.json`, dry);
12
+ }
13
+ }
14
+ //# sourceMappingURL=allows-tooling-in-claude-settings-check.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"allows-tooling-in-claude-settings-check.js","sourceRoot":"","sources":["../../src/checks/allows-tooling-in-claude-settings-check.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAErC,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAA;AAEhE;;;;GAIG;AACH,MAAM,OAAO,kCAAmC,SAAQ,mBAAmB;IACvE,MAAM,CAAmB,SAAS,GAAG,+BAA+B,CAAA;IAEpE,GAAG,CAAC,GAAG,GAAG,KAAK;QACX,OAAO,IAAI,CAAC,cAAc,CACtB,OAAO,EACP,MAAM,EAAE,CAAC,OAAO,CAAC,qBAAqB,CAAC,EACvC,CAAC,KAAK,EAAE,EAAE,CAAC,yBAAyB,KAAK,iDAAiD,EAC1F,GAAG,CACN,CAAA;IACL,CAAC;CACJ"}
@@ -0,0 +1,21 @@
1
+ import { type CheckResult, FixableCheck } from './check.js';
2
+ /**
3
+ * Biome's `$schema` must resolve out of node_modules rather than name a released
4
+ * version. The local path always describes the Biome that is actually installed,
5
+ * so upgrading Biome does not also mean editing biome.json — a versioned remote
6
+ * URL leaves the editor validating against whichever version was current when
7
+ * someone last remembered to bump the line.
8
+ *
9
+ * A project without Biome is none of this check's business: no config file means
10
+ * pass, not a warning.
11
+ *
12
+ * Both the detection and the fix work on the raw text rather than a decoded
13
+ * document, for two reasons: Biome reads its config as JSONC, so a file with
14
+ * comments is valid and must not be reported as broken; and biome.json is
15
+ * formatted by Biome itself, so re-encoding the whole file would leave
16
+ * `biome ci` failing on the very file this fix just touched.
17
+ */
18
+ export declare class BiomeUsesLocalSchemaCheck extends FixableCheck {
19
+ static readonly checkName = "biomeUsesLocalSchema";
20
+ fix(dry?: boolean): CheckResult;
21
+ }
@@ -0,0 +1,78 @@
1
+ import { policy } from '../policy.js';
2
+ import { FixableCheck } from './check.js';
3
+ /** The `$schema` string literal, captured with its JSON escapes intact. */
4
+ const SCHEMA_PATTERN = /"\$schema"\s*:\s*"((?:[^"\\]|\\.)*)"/;
5
+ /**
6
+ * Biome's `$schema` must resolve out of node_modules rather than name a released
7
+ * version. The local path always describes the Biome that is actually installed,
8
+ * so upgrading Biome does not also mean editing biome.json — a versioned remote
9
+ * URL leaves the editor validating against whichever version was current when
10
+ * someone last remembered to bump the line.
11
+ *
12
+ * A project without Biome is none of this check's business: no config file means
13
+ * pass, not a warning.
14
+ *
15
+ * Both the detection and the fix work on the raw text rather than a decoded
16
+ * document, for two reasons: Biome reads its config as JSONC, so a file with
17
+ * comments is valid and must not be reported as broken; and biome.json is
18
+ * formatted by Biome itself, so re-encoding the whole file would leave
19
+ * `biome ci` failing on the very file this fix just touched.
20
+ */
21
+ export class BiomeUsesLocalSchemaCheck extends FixableCheck {
22
+ static checkName = 'biomeUsesLocalSchema';
23
+ fix(dry = false) {
24
+ const configFile = policy().string('biome.configFile');
25
+ const contents = this.project.read(configFile);
26
+ if (contents === null) {
27
+ return 'pass';
28
+ }
29
+ if (!contents.includes('{')) {
30
+ this.comment(`${configFile} is empty or unreadable`);
31
+ return 'fail';
32
+ }
33
+ const expected = policy().string('biome.schema');
34
+ const declared = declaredSchema(contents);
35
+ if (declared === expected) {
36
+ return 'pass';
37
+ }
38
+ this.comment(declared === null
39
+ ? `Missing "$schema" in ${configFile}: set it to "${expected}" so the schema follows the installed Biome`
40
+ : `Invalid "$schema" in ${configFile}: must be "${expected}" (found "${declared}"), so it does not need bumping on every Biome update`);
41
+ if (dry) {
42
+ return 'fail';
43
+ }
44
+ this.project.write(configFile, declared === null ? insertSchema(contents, expected) : replaceSchema(contents, expected));
45
+ return this.fix(true);
46
+ }
47
+ }
48
+ /** The decoded `$schema` value, or null if the file declares none. */
49
+ function declaredSchema(contents) {
50
+ const literal = SCHEMA_PATTERN.exec(contents)?.[1];
51
+ if (literal === undefined) {
52
+ return null;
53
+ }
54
+ try {
55
+ const decoded = JSON.parse(`"${literal}"`);
56
+ return typeof decoded === 'string' ? decoded : null;
57
+ }
58
+ catch {
59
+ return null;
60
+ }
61
+ }
62
+ function replaceSchema(contents, expected) {
63
+ // A function, not a replacement string: the literal "$schema" would otherwise
64
+ // have to be escaped against `$`-pattern interpolation.
65
+ return contents.replace(SCHEMA_PATTERN, () => `"$schema": ${JSON.stringify(expected)}`);
66
+ }
67
+ /**
68
+ * Prepends the key inside the top-level object, indented like whatever key
69
+ * currently comes first — Biome's own convention is `$schema` first.
70
+ */
71
+ function insertSchema(contents, expected) {
72
+ const open = contents.indexOf('{');
73
+ const rest = contents.slice(open + 1);
74
+ const indent = /\n([ \t]+)\S/.exec(rest)?.[1] ?? ' ';
75
+ const separator = rest.trim() === '}' ? '' : ',';
76
+ return `${contents.slice(0, open + 1)}\n${indent}"$schema": ${JSON.stringify(expected)}${separator}${rest}`;
77
+ }
78
+ //# sourceMappingURL=biome-uses-local-schema-check.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"biome-uses-local-schema-check.js","sourceRoot":"","sources":["../../src/checks/biome-uses-local-schema-check.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import { type CheckResult, FixableCheck } from './check.js';
2
+ /**
3
+ * The runner must actually run. The PHP counterpart hooks composer's
4
+ * post-update-cmd; npm has no equivalent lifecycle hook that survives npm 12's
5
+ * script blocking, so the JS project runs it from `ci-lint` instead — the script
6
+ * the CI template and the Claude Stop hook both already invoke.
7
+ */
8
+ export declare class CallsBaselineCheck extends FixableCheck {
9
+ static readonly checkName = "callsBaseline";
10
+ fix(dry?: boolean): CheckResult;
11
+ }
@@ -0,0 +1,34 @@
1
+ import { policy } from '../policy.js';
2
+ import { FixableCheck } from './check.js';
3
+ /**
4
+ * The runner must actually run. The PHP counterpart hooks composer's
5
+ * post-update-cmd; npm has no equivalent lifecycle hook that survives npm 12's
6
+ * script blocking, so the JS project runs it from `ci-lint` instead — the script
7
+ * the CI template and the Claude Stop hook both already invoke.
8
+ */
9
+ export class CallsBaselineCheck extends FixableCheck {
10
+ static checkName = 'callsBaseline';
11
+ fix(dry = false) {
12
+ const command = policy().string('ciLint.baselineCommand.js');
13
+ const packageJson = this.project.readJson('package.json');
14
+ if (packageJson === null) {
15
+ this.comment('Package.json missing: Create package.json in project root');
16
+ return 'fail';
17
+ }
18
+ const script = packageJson.scripts?.['ci-lint'];
19
+ if (script?.includes(command)) {
20
+ return 'pass';
21
+ }
22
+ this.comment(`ci-lint must run the baseline: add "${command}" to the ci-lint script in package.json`);
23
+ if (dry) {
24
+ return 'fail';
25
+ }
26
+ packageJson.scripts = {
27
+ ...packageJson.scripts,
28
+ 'ci-lint': script === undefined || script.trim() === '' ? command : `${script} && ${command}`,
29
+ };
30
+ this.project.writeJson('package.json', packageJson);
31
+ return this.fix(true);
32
+ }
33
+ }
34
+ //# sourceMappingURL=calls-baseline-check.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"calls-baseline-check.js","sourceRoot":"","sources":["../../src/checks/calls-baseline-check.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AACrC,OAAO,EAAoB,YAAY,EAAE,MAAM,YAAY,CAAA;AAM3D;;;;;GAKG;AACH,MAAM,OAAO,kBAAmB,SAAQ,YAAY;IAChD,MAAM,CAAmB,SAAS,GAAG,eAAe,CAAA;IAEpD,GAAG,CAAC,GAAG,GAAG,KAAK;QACX,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,MAAM,CAAC,2BAA2B,CAAC,CAAA;QAC5D,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAc,cAAc,CAAC,CAAA;QAEtE,IAAI,WAAW,KAAK,IAAI,EAAE,CAAC;YACvB,IAAI,CAAC,OAAO,CAAC,2DAA2D,CAAC,CAAA;YAEzE,OAAO,MAAM,CAAA;QACjB,CAAC;QAED,MAAM,MAAM,GAAG,WAAW,CAAC,OAAO,EAAE,CAAC,SAAS,CAAC,CAAA;QAE/C,IAAI,MAAM,EAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;YAC5B,OAAO,MAAM,CAAA;QACjB,CAAC;QAED,IAAI,CAAC,OAAO,CAAC,uCAAuC,OAAO,yCAAyC,CAAC,CAAA;QAErG,IAAI,GAAG,EAAE,CAAC;YACN,OAAO,MAAM,CAAA;QACjB,CAAC;QAED,WAAW,CAAC,OAAO,GAAG;YAClB,GAAG,WAAW,CAAC,OAAO;YACtB,SAAS,EAAE,MAAM,KAAK,SAAS,IAAI,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,MAAM,OAAO,OAAO,EAAE;SAChG,CAAA;QAED,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,cAAc,EAAE,WAAW,CAAC,CAAA;QAEnD,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;IACzB,CAAC;CACJ"}
@@ -0,0 +1,37 @@
1
+ import type { Project } from '../project.js';
2
+ /** Mirrors Limenet\LaravelBaseline\Enums\CheckResult, values included. */
3
+ export type CheckResult = 'pass' | 'fail' | 'warn';
4
+ export declare function isError(result: CheckResult): boolean;
5
+ export declare function icon(result: CheckResult): string;
6
+ /** Collects the actionable messages a single check run produced. */
7
+ export declare class CommentCollector {
8
+ private comments;
9
+ add(comment: string): void;
10
+ all(): string[];
11
+ reset(): void;
12
+ }
13
+ export declare abstract class Check {
14
+ protected readonly project: Project;
15
+ protected readonly comments: CommentCollector;
16
+ /**
17
+ * The check's stable identifier — the same string the PHP runner derives from
18
+ * its class name, and the key used in .baseline.json excludes and in fixtures.
19
+ * Declared explicitly rather than derived, so a bundler renaming a class can
20
+ * never silently rename a check.
21
+ */
22
+ static readonly checkName: string;
23
+ constructor(project: Project, comments: CommentCollector);
24
+ abstract check(): CheckResult;
25
+ protected comment(message: string): void;
26
+ get name(): string;
27
+ }
28
+ /**
29
+ * A check that can repair what it detects. check() is the dry run, so detection
30
+ * and repair can never drift apart — the same shape as the PHP
31
+ * AbstractFixableCheck.
32
+ */
33
+ export declare abstract class FixableCheck extends Check {
34
+ check(): CheckResult;
35
+ abstract fix(dry?: boolean): CheckResult;
36
+ }
37
+ export declare function isFixable(check: Check): check is FixableCheck;