Files
InvoiceShelf/AGENTS.md
T
Darko Gjorgjijoski 8fe6975a81 chore: drop laravel/boost, make AGENTS.md the source of truth (#721)
* chore: drop laravel/boost, make AGENTS.md the source of truth

The two agent files were inverted here relative to 3.x. AGENTS.md held
nothing but Laravel Boost's generated guidelines, while CLAUDE.md carried
the actual project guide — so an agent reading AGENTS.md, the file the
convention points at, got generic Laravel advice and none of the
specifics for this repository.

Boost is not used, so it goes rather than being worked around: the
dependency, the boost:update hook on post-update-cmd, the .cursor MCP
config that existed only to launch boost:mcp, and the three skill
packages it installed there.

AGENTS.md now carries the project guide and is the committed source of
truth. CLAUDE.md, GEMINI.md and .github/copilot-instructions.md become
gitignored symlinks to it, created by bin/ai-docs.php from
post-autoload-dump — the same arrangement, and the same script, as 3.x.

* chore: regenerate .phpstorm.meta.php

Removes every boost.* entry now that the package is gone. Most of the
diff is unrelated churn — the file had not been regenerated in a while,
so it also picks up drift in routes and config. Kept separate so the
boost removal stays readable.
2026-07-29 17:39:20 +02:00

119 lines
5.8 KiB
Markdown

# AGENTS.md
Canonical guide for AI coding agents working in this repository. The tool-specific files
(`CLAUDE.md`, `GEMINI.md`, `.github/copilot-instructions.md`) are gitignored symlinks to this file —
run `composer run ai-docs` to (re)create them.
## Project Overview
InvoiceShelf is an open-source invoicing and expense tracking application built with Laravel 13 (PHP 8.4) and Vue 3. It supports multi-company tenancy, customer portals, recurring invoices, and PDF generation.
## Common Commands
### Development
```bash
composer run dev # Starts PHP server, queue listener, log tail, and Vite dev server concurrently
pnpm dev # Vite dev server only
pnpm build # Production frontend build
```
### Testing
```bash
php artisan test --compact # Run all tests
php artisan test --compact --filter=testName # Run specific test
./vendor/bin/pest --stop-on-failure # Run via Pest directly
make test # Makefile shortcut
```
Tests use SQLite in-memory DB, configured in `phpunit.xml`. Tests seed via `DatabaseSeeder` + `DemoSeeder` in `beforeEach`. Authenticate with `Sanctum::actingAs()` and set the `company` header.
### Code Style
```bash
vendor/bin/pint --dirty --format agent # Fix style on modified PHP files
vendor/bin/pint --test # Check style without fixing (CI uses this)
```
### Artisan Generators
Always use `php artisan make:*` with `--no-interaction` to create new files (models, controllers, migrations, tests, etc.).
## Architecture
### Multi-Tenancy
Every major model has a `company_id` foreign key. The `CompanyMiddleware` sets the active company from the `company` request header. Bouncer authorization is scoped to the company level via `DefaultScope` (`app/Bouncer/Scopes/DefaultScope.php`).
### Authentication
Three guards: `web` (session), `api` (Sanctum tokens for `/api/v1/`), `customer` (session for customer portal). API routes use `auth:sanctum` middleware; customer portal uses `auth:customer`.
### Routing
- **API**: All endpoints under `/api/v1/` in `routes/api.php`, grouped with `auth:sanctum`, `company`, and `bouncer` middleware
- **Web**: `routes/web.php` serves PDF endpoints, auth pages, and catch-all SPA routes (`/admin/{vue?}`, `/{company:slug}/customer/{vue?}`)
### Frontend
- Entry point: `resources/scripts/main.js`
- Vue Router: `resources/scripts/admin/admin-router.js` (admin), `resources/scripts/customer/customer-router.js` (customer portal)
- State: Pinia stores in `resources/scripts/admin/stores/`
- Path aliases: `@` = `resources/`, `$fonts`, `$images` for static assets
- Vite dev server expects `invoiceshelf.test` hostname
### Backend Patterns
- **Authorization**: Silber/Bouncer with policies in `app/Policies/`. Controllers use `$this->authorize()`.
- **Validation**: Form Request classes, never inline validation
- **API responses**: Eloquent API Resources in `app/Http/Resources/`
- **PDF generation**: DomPDF (`GeneratesPdfTrait`) or Gotenberg
- **Email**: Mailable classes with `EmailLog` tracking
- **File storage**: Spatie MediaLibrary, supports local/S3/Dropbox
- **Serial numbers**: `SerialNumberFormatter` service
- **Company settings**: `CompanySetting` model (key-value per company)
### Database
Supports MySQL, PostgreSQL, and SQLite. Prefer Eloquent over raw queries. Use `Model::query()` instead of `DB::`. Use eager loading to prevent N+1 queries.
## Code Conventions
- PHP: snake_case, constructor property promotion, explicit return types, PHPDoc blocks over inline comments
- JS: camelCase
- Always check sibling files for patterns before creating new ones
- Use `config()` helper, never `env()` outside config files
- Every change must have tests (feature tests preferred over unit tests)
- Run `vendor/bin/pint --dirty --format agent` after modifying PHP files
## Releasing
Releases are cut by pushing a tag. Nothing is typed into GitHub by hand.
```bash
# 1. Add a "## <version> — <date>" section to CHANGELOG.md and bump version.md,
# in a PR like any other change — the notes are reviewed with the code.
# 2. Once merged, tag the merge commit:
git tag 2.4.3 && git push origin 2.4.3
```
`release.yaml` then runs the tests, reads the `CHANGELOG.md` section for that tag,
builds the package with `make clean dist`, and creates a **draft** release with the
zip attached. It stops there and prints the draft URL in the run summary.
**You publish the draft yourself.** That is deliberate, not an omission: GitHub does
not start workflow runs from events created with `GITHUB_TOKEN`, so a release
published by the workflow reaches nothing downstream. Pressing Publish fires
`release: published` under your own identity, which triggers `docker.yaml` to
register the release on the updater and build the Docker images. **Until you
publish, no install is offered anything.**
Notes on the mechanics:
- **A tag with no `CHANGELOG.md` section fails the run** before anything is built,
so a release can never go out with empty notes.
- **`prerelease` and "mark as latest" are set on the draft**, derived from the tag: a
`-` suffix (`2.4.3-beta.1`) means pre-release, which routes it to the **insider**
channel so ordinary installs are not offered it. "Latest" is gated on
`LATEST_MAJOR` in the workflows — bump it there when 3.x becomes the stable line.
- **If registration fails**, re-run it without cutting a new release: run the
`Docker Build and Push` workflow manually with `register_tag` set to the version.
That path is idempotent and does not rebuild the Docker images.
- `.github/scripts/changelog-section.php <version>` prints what the updater will be
sent, so you can check the notes locally before tagging.
## CI Pipeline
GitHub Actions (`check.yaml`): runs Pint style check, then builds frontend and runs Pest tests on PHP 8.4.