Modelo de AGENTS.md
Copie este ficheiro para a raiz de um repositório de produto novo como AGENTS.md, como pede o ADR-0026. Os agentes de código leem-no antes de mudarem o que quer que seja, para seguirem as mesmas predefinições que as pessoas.
- Preencha a secção Product; tudo o resto é a predefinição da empresa
- A tabela Stack é construída a partir dos ADRs deste site. Quando um ADR muda, copie o ficheiro de novo ou atualize as linhas que mudaram
- As decisões do produto prevalecem: um ADR do produto em
docs/decisions/sobrepõe-se a uma predefinição daqui, e o ficheiro di-lo - O ficheiro fica em inglês, a língua em que os agentes e o código trabalham
# AGENTS.md
Instructions for coding agents and people working in this repository. The company defaults come from Visual Thinking's Stack Decisions site; each line cites the ADR that sets it.
## Product
<!-- Fill in, then delete this comment. -->
- **Product:** `<product>` — what it does, in one line- **Apps:** `apps/web/` (TanStack Start). Add each new deployable under `apps/`- **Product decisions:** `docs/decisions/`. A product ADR overrides the defaults below; read them first
## Commands
Run from the repository root unless noted. Bun is the runtime, package manager and test runner: use `bun` and `bunx` instead of `node`, `npm`, `npx` or `pnpm` (ADR-0035).
| Task | Command || ---------------------------------------------------- | ------------------------------- || Install dependencies | `bun install` || Dev server (in `apps/web/`) | `bun run dev` || Format, lint, type-check | `bun run check` (`bun run fix`) || Unit and component tests (`*.test.ts`, `*.test.tsx`) | `bun run test` || Integration tests (`*.int.test.ts`, Docker) | `bun run test:int` || Production build | `bun run build` || End-to-end tests (`e2e/*.spec.ts`, after build) | `bun run test:e2e` || Local Postgres and Valkey | `docker compose up -d` |
Before calling work done: `bun run check`, `bun run test` and `bun run test:int` pass.
## Stack
One standard per layer. Don't add a library that overlaps one of these; propose a product ADR instead.
| Layer | Standard | ADR || --- | --- | --- || UI framework | React | ADR-0001 || App framework | TanStack Start | ADR-0002 || Deploy target | Docker, self-hosted, on Bun | ADR-0003 || Input validation | Zod | ADR-0005 || Database | PostgreSQL | ADR-0007 || ORM / query layer | Drizzle ORM v1 + Drizzle Kit on Bun SQL | ADR-0008 || Auth | Better Auth 1.7 | ADR-0009 || Rate limiting | rate-limiter-flexible + Valkey | ADR-0010 || Styling | Tailwind CSS v4 + shadcn/ui on Base UI | ADR-0011 || Data fetching & state | TanStack Query + loaders; URL + React state | ADR-0012 || Forms | TanStack Form + Zod + shadcn/ui Field | ADR-0013 || Testing | bun test + Testcontainers; Playwright E2E | ADR-0014 || Observability | OpenTelemetry + GlitchTip | ADR-0015 || CI/CD | One workflow for GitHub + Forgejo; Nodus deploys | ADR-0016 || Reverse proxy & TLS | Traefik 3.7, no CDN for now | ADR-0017 || Tables | TanStack Table v9 + shadcn/ui Table | ADR-0018 || Virtual lists | TanStack Virtual | ADR-0019 || Charts | TanStack Charts | ADR-0020 || Markdown | TanStack Markdown | ADR-0021 || Syntax highlighting | TanStack Highlight | ADR-0022 || Debounce & throttle | TanStack Pacer, pinned | ADR-0023 || Keyboard shortcuts | TanStack Hotkeys, pinned | ADR-0024 || AI SDK | TanStack AI, pinned; provider per product | ADR-0025 || Project structure | Bun workspace; layer folders; src/server/ fenced | ADR-0026 || Engineering principles | KISS, YAGNI, SOLID, in that order | ADR-0027 || Background jobs & scheduling | BullMQ on Valkey | ADR-0028 || Transactional email | React Email + Nodemailer over SMTP | ADR-0029 || File & object storage | S3 API on Ceph or any S3-compatible store | ADR-0030 || Internationalization | Paraglide JS | ADR-0031 || Postgres backup & restore | Defined per deployment | ADR-0032 || Authorization | Better Auth access control | ADR-0033 || Dates, time zones & money | date-fns 4 + Intl; money in minor units | ADR-0034 || Runtime & toolchain | Bun, built-ins first | ADR-0035 || API layer | Start server routes at /api/* | ADR-0036 |
## Where code goes (ADR-0026)
Inside `apps/web/src/`, the folder says what a file does and the file name says which domain it belongs to. Imports only flow down this list:
- `routes/` — route files only: loader, guards and the page. Folder syntax: `invoices/$invoiceId.tsx`, `_authed/`, `(group)/`- `components/` — `ui/` is shadcn/ui (ours to edit); `form/`, `chart/`, `virtual/`, `markdown/` are shared wrappers; product components go in `components/<domain>/`- `hooks/` — React hooks shared by several components- `functions/` — server functions, one file per domain. Only their handlers may import from `server/`- `schemas/` — Zod schemas shared by forms, server functions and API routes- `lib/` — client-safe helpers: no React, no server imports- `server/` — server-only code, fenced by import protection: a `server/` import that reaches the browser fails the build. Nothing outside `server/` imports it, except server function handlers and the API route files in `routes/api/`. Imports from `bun` stay inside `server/` (ADR-0035)
More placement rules:
- Business rules live in `server/services/<domain>.ts` as plain functions that take their dependencies as parameters. Handlers parse input with the shared schema, check access, and call the service- API endpoints are Start server routes: `routes/api/v1/<domain>.ts` only maps methods to the handlers in `server/api/v1/<domain>.ts`. Better Auth is `routes/api/auth/$.ts`; webhooks go in `routes/api/webhooks/` (ADR-0036)- Tables: one file per domain in `server/db/schema/`, re-exported by `schema/index.ts`. `server/db/schema/auth.ts` is generated by Better Auth's CLI; never edit it. `server/db/client.ts` is the only file that connects. Migrations are generated by Drizzle Kit into `drizzle/`- Environment variables: only `server/env.ts` reads `process.env`, through T3 Env's `createEnv`. A new variable goes there and in `.env.example`- Queues: one file per queue in `server/jobs/`, with its payload schema and handler. Jobs run in the worker entry point, never in the web process (ADR-0028)- Only `server/mail/` imports `nodemailer`, with templates in `server/mail/templates/` (ADR-0029); only `server/storage/` imports Bun's `S3Client` (ADR-0030)- When Bun has a built-in, use it instead of a package: `Bun.SQL`, `S3Client`, `Bun.password`, `bun:test`, `Bun.$`, `Bun.file`, `Bun.Glob`. ADR-0035 lists the exceptions, such as `ioredis`- Roles and permissions: `lib/permissions.ts`, imported by the server and the client auth plugins (ADR-0033)- Tests sit next to their code and import from `bun:test`. Component tests start with `// @vitest-environment happy-dom`; focus, keyboard and pointer behavior is tested with Playwright in `e2e/` (ADR-0014)- Spans: `src/start.ts` traces every request (`trace`) and every server function (`traceServerFn`); `server/db/client.ts` hands Drizzle the client wrapped by `traceSql`; calls in `server/storage/` and `server/mail/` run inside `withSpan`. The helpers live in `server/telemetry.ts`; don't add spans by hand elsewhere (ADR-0015)- Valkey spans record the command name only — keys hold session tokens, IPs and emails (ADR-0015)
## Names (ADR-0026)
- Files and folders are kebab-case. One component per file, as a named export: `invoice-table.tsx` exports `InvoiceTable`. No default exports unless a tool requires one- Hooks are `use-<name>.ts`, exporting `use<Name>`. Domain names are plural and match the URL: `/invoices`, `functions/invoices.ts`, the `invoices` table- Suffixes only for what tools read: `.test.ts`, `.test.tsx`, `.int.test.ts`, `.spec.ts`, `.server.ts`, `.client.ts`, `.gen.ts`. No role suffixes such as `.service.ts`- Import with the `@/` alias across folders; relative imports stay within one folder
## Rules that are easy to break
Data and validation:
- Server functions are public endpoints: validate every input with Zod in `.validator()`; API handlers use `readJson` and `readQuery` from `server/api/http.ts` (ADR-0005, ADR-0036)- Reads are `GET` server functions; writes are `POST` server functions called through `useMutation`, so every write passes the rate limiter. After a write, invalidate the owning query key (ADR-0012)- Query options live in `queryOptions()` factories, with keys that start with the entity: `["invoices", invoiceId]` (ADR-0012)- Shareable UI state lives in the URL: Zod 4 schemas in `validateSearch`, with `.catch()` fallbacks. Don't install `@tanstack/zod-adapter` (ADR-0012)- Server functions serve our own UI; server routes at `/api/*` serve everything consumed as an HTTP API. Don't add Hono or tRPC without a product ADR (ADR-0036)- `/api/v1/*` callers authenticate with the `Authorization` header only; never accept the session cookie there, because those routes have no CSRF check (ADR-0036)- Job handlers are idempotent, and a job is enqueued after the Postgres commit (ADR-0028)- User-facing text comes from Paraglide's `m.*()` functions; outside a request, pass `{ locale }` (ADR-0031)- Moments are `timestamptz`, calendar days are `date` strings. Format dates with `Intl` and an explicit `timeZone`. Money is integer minor units plus a currency code, never a float (ADR-0034)
Security and privacy:
- Every write server function checks the session itself; a route's `beforeLoad` guards the page, not the endpoint (ADR-0013)- Every handler checks a permission, never a role name, and scopes rows with a `where` built from the session; a row outside scope is `404` (ADR-0033)- Uploads: the server picks the object key and checks the object with `file(key).stat()` before trusting it; buckets stay private (ADR-0030)- Better Auth email callbacks enqueue the send and return without awaiting it (ADR-0029)- Never log passwords, tokens, secrets, cookies or authorization headers, nor request bodies on auth endpoints. Record user IDs, never emails or names (ADR-0015)- No URL query string leaves the process in telemetry; `instrumentation.ts` filters them, and a guard test checks it (ADR-0015)- Model calls run only on the server, and model output is untrusted input (ADR-0025)- The guard tests must stay green (ADR-0014)
UI:
- Tailwind v4 through `@tailwindcss/vite`, with tokens as CSS variables in `src/styles/app.css`. No `tailwind.config.js`, no CSS-in-JS (ADR-0011)- Primitives come from `@base-ui/react`, never the deprecated `@base-ui-components/react` (ADR-0011)- Forms use `useAppForm` and `form.AppField` from `components/form/`, with one Zod schema per form, shared with its server function (ADR-0013)- Render Markdown through `components/markdown/`; raw HTML stays off (ADR-0021)
Tests:
- No real third-party calls: mock external HTTP with MSW (`onUnhandledFrame: "error"`), and use a fake model for AI (ADR-0014, ADR-0025). Mail goes to a `jsonTransport: true` transporter (ADR-0029)
Dependencies:
- Bun only; `bun.lock` is the only lockfile. Don't add packages to `trustedDependencies` without review (ADR-0035)- Pre-1.0 libraries (TanStack Pacer, Hotkeys and AI, T3 Env) are pinned to exact versions with `bun add --exact`; upgrade them deliberately- `@opentelemetry/*` packages move together (ADR-0015)- No new abstraction, option or dependency without a current requirement that needs it (ADR-0027)
## Commits and pull requests
- Pull requests are squash-merged; the title becomes the commit on `main`, so it must be a Conventional Commit: `<type>(<scope>)!: <summary>`, such as `feat(invoices): export to CSV` (ADR-0026)- Types: `feat`, `fix`, `perf`, `refactor`, `test`, `docs`, `style`, `build`, `ci`, `chore`, `revert`. The scope is optional: a domain or an app- `!` marks a change someone must act on before a deploy: a public API route removed or changed, a new required environment variable, or the contract step of a migration- Branch commits are free-form
