# AGENTS.md

This is a subscription SaaS built with Laravel, Eloquent, and Stripe via Cashier.

## Project

Stack: PHP 8.3, Laravel 11, MySQL, Redis queues, Laravel Cashier, Inertia with Vue 3, Vite, Pest, Pint, Larastan.

Layout:

- `routes/web.php`, `routes/api.php`, `routes/console.php`
- `app/Http/Controllers/` thin controllers, one action group per resource
- `app/Http/Requests/` form request validation
- `app/Http/Middleware/`
- `app/Actions/` single purpose invokable classes holding business logic
- `app/Models/` Eloquent models, relationships, casts, scopes
- `app/Policies/` authorization
- `app/Jobs/` queued work, `app/Events/` and `app/Listeners/`
- `database/migrations/`, `database/factories/`, `database/seeders/`
- `resources/js/Pages/` Inertia pages, `resources/js/Components/`
- `config/` framework and package configuration

Entry points: `routes/web.php`, `app/Providers/AppServiceProvider.php`, `config/cashier.php`.

## Commands

```bash
composer install
npm install
php artisan serve                       # dev server on :8000
npm run dev                             # vite dev server
npm run build                           # production assets
php artisan migrate                     # apply migrations
php artisan migrate:fresh --seed        # local reset only
php artisan make:model Invoice -mfR     # model, migration, factory, request, controller
php artisan queue:work                  # process jobs
php artisan schedule:work               # run the scheduler locally
php artisan test                        # full suite
php artisan test --filter=SubscriptionTest
./vendor/bin/pint                       # format to PSR-12
./vendor/bin/phpstan analyse            # static analysis
php artisan optimize:clear              # clear config, route, view caches
```

Generate classes with artisan rather than creating files by hand, so namespaces and stubs stay correct.

## Code style

- Controllers are thin. Validation goes in a form request, authorization in a policy, business logic in an action.
- Never call `request()` outside a controller or middleware. Pass data down explicitly.
- No queries in Blade or in an Inertia page component. Load relations eagerly with `with()` in the controller.
- Guard against N+1 by keeping `Model::preventLazyLoading()` enabled in non production environments.
- Use route model binding rather than manual `find()` plus 404 handling.
- Mass assignment: define `$fillable` explicitly, never `$guarded = []`.
- Money is stored in integer minor units, formatted only at the view layer.
- Anything slower than a database write goes into a queued job, not an inline call.
- Follow PSR-12 through Pint. Typed properties, typed arguments, and return types everywhere.
- Config values are read through `config()`, never `env()` outside `config/`.

## Boundaries

Do not touch without explicit instruction:

- Existing files in `database/migrations/`. Migrations are append only once applied.
- `.env` and any credential. New keys go in `.env.example` and a `config/` file.
- `composer.lock` and `package-lock.json`. Use `composer require` and `npm install <pkg>`.
- Cashier tables, subscription records, and Stripe price identifiers.
- `.github/workflows/`, deployment scripts, and server provisioning files.

Needs human review: policies and gates, anything in `app/Jobs/` that touches billing, webhook handling, and any migration that drops or renames a column.

Do not run `migrate:fresh` against anything but the local database.

## Testing

- `php artisan test` runs Pest. Use `--filter` to run one test while iterating.
- Tests use `RefreshDatabase` against a dedicated test connection. Never point tests at the development database.
- Required tests: every route (happy path plus authorization failure), every policy, every job, and all billing state transitions.
- Build data with factories in `database/factories/`. Seeders are for local development only.
- Fake external services with `Http::fake()`, `Queue::fake()`, `Mail::fake()`. Do not hit Stripe in tests, use Cashier's test helpers.
- A bug fix ships with a regression test.

## Git workflow

- Branch from `main`: `feat/short-description`, `fix/short-description`.
- Conventional commits: `feat(billing): add annual plan upgrade path`.
- Migrations ship in the same commit as the code that depends on them.
- Run Pint before committing so formatting never appears in a review diff.
- PR description lists migrations, queue changes, new config keys, and any artisan command needed at deploy.
- Never commit to `main`.
