Quality and testing
The template ships a small, real quality bar: Pest for PHP, Vitest for the Vite/Echo surface, Pint for format, PHPStan level 5 for analysis, and composer audit for advisories. Security and permission tests are first-class — a module that cannot show guest, wrong-role, wrong-owner, and happy-path is not done (ADR 0002).
Testing doctrine
Security and permission matrices sit beside every first-class system: media, realtime, and any future module. They are not a polish pass after the UI works.
- Pest covers
tests/Unit,tests/Feature,tests/Feature/Api,tests/Feature/Security. - Each matrix asserts four cases: guest (unauthenticated), wrong-role (authenticated, missing permission), wrong-owner (authenticated, IDOR), happy-path.
- Reusable helpers (
actingAsRole,actingAsPassport,assertForbiddenTo,assertCannotAccessOthersRecord) live in the template so a later resource copies the matrix instead of inventing rules. - Pest uses sqlite
:memory:(pinned inTestCase) andStorage::fake()for S3/media. Compose MinIO and libSQL are for humans, not CI. Channel auth for Reverb is HTTP (Broadcast::auth); websocket frames are out of scope. - Vitest covers
resources/js— the Echo client and any Vite surface. No leaked cloud keys.
Stay on Pest 4 while php is ^8.3. Pest 5 and the arch plugin v5 require PHP ^8.4.
Pest arch()
Architecture-rule tests (arch()->expect(), presets) are in scope as guidelines, not locked rules. Recommended baseline: php() + security() + no-debug. Do not enable laravel() or strict() wholesale — they encode a hexagonal style this template never chose. Enforcement lands once tooling/04 then 05 close; not in the first harness.
Larastan / PHPStan
Level 5, paths: [app/] only. No Psalm. Bumping to level 6 is a human call — Filament core runs at 6 and ignores missingType.generics; the template does not inherit that automatically.
laravel/pao
Agent-only JSON/ANSI wrapper around Pest, PHPStan, and Artisan. Skeleton require-dev. It is not a linter and not Boost — keep it.
composer audit
Packagist advisory check, should-have in CI with --locked. Preferred over roave/security-advisories as the template default.
Quality CI
GitHub Actions live at template/.github/workflows/, working-directory: app, on host PHP + sqlite — not Docker, FrankenPHP, or libSQL. Jobs: Pest, pint --test, PHPStan, npm test, npm run build, composer audit. CI is the gate.
Frontend lint
Biome is the pick if a frontend tool ships — one JS lint+format binary. Laravel 13 and the Livewire kit ship none, so the template does not install it. Echo stays as .js.
Pre-commit
Husky / lint-staged is optional for staged Pint + JS. The git root is template/ and package.json is app/, so the recipe is nested. Researchers split: ship it, or skip it — CI is the gate either way.
Local commands
docker compose exec app artisan test
docker compose exec app vendor/bin/pint --test
docker compose exec app vendor/bin/phpstan analyse
docker compose exec app composer audit --locked
docker compose exec app npm test