b5321c5e8e
The documented gates had drifted well below the real ones: the backend gate reads 80 in the workflow, not 50, and the frontend thresholds are 96 lines and statements, 91 functions, 92 branches, not the 80/85 pair recorded here. Anyone trusting the old numbers would think they had headroom that CI does not grant. The frontend type-check command is also named correctly now, since invoking tsc through npx picks up a newer compiler and fails spuriously.
238 lines
16 KiB
Markdown
238 lines
16 KiB
Markdown
# AGENTS.md
|
|
|
|
Operating guide for humans and AI agents working in **Föräldraledigplannerare** — a web app for planning Swedish parental leave (alone or with a partner), folding in vacation days to optimize leave over time until the child turns 4 (after which only 96 days may be saved).
|
|
|
|
This is a **full-stack monorepo**: a TypeScript/React/Vite frontend and a Python/FastAPI backend.
|
|
|
|
---
|
|
|
|
## 🛑 DIRECTORY NAVIGATION & FILE DISCOVERY
|
|
|
|
**Context:** This repo contains massive generated/dependency folders (`node_modules`, `dist`, `build`, `.venv`/`venv`, `__pycache__`, `.mypy_cache`, `.ruff_cache`, `graphify-out`, etc.). Unrestricted file listings will bloat your context window, crash your session, and cause severe token waste.
|
|
|
|
* **NEVER use `ls -R`:** This command is strictly forbidden in this repository.
|
|
* **NEVER use `find` without `-maxdepth`:** Unbounded `find` crawls dependency trees just like `ls -R`. Always add `-maxdepth 3` or tighter.
|
|
* **Primary Discovery:** To map out files or explore the project tree, **ALWAYS use `git ls-files`**. This is your default command and ensures you only interact with human-written source code.
|
|
* **Structural Context:** If you explicitly need a visual directory layout to understand architecture, use `tree` with the mandatory ignore flags:
|
|
|
|
```bash
|
|
tree -I 'node_modules|.git|dist|build|__pycache__|venv|.venv|env|.pytest_cache|.mypy_cache|.ruff_cache|graphify-out|*.egg-info'
|
|
```
|
|
|
|
* **Searching Code:** To search for text or code across the repo, **ALWAYS use `git grep "search_term"`** instead of `grep -r` to avoid crawling ignored or minified assets.
|
|
* **NEVER** go outside the current directory; always use relative paths based on "here".
|
|
|
|
---
|
|
|
|
## graphify
|
|
|
|
This project has a knowledge graph at `graphify-out/` with god nodes, community structure, and cross-file relationships.
|
|
|
|
When the user types `/graphify`, invoke the `skill` tool with `skill: "graphify"` before doing anything else.
|
|
|
|
Rules:
|
|
|
|
* For codebase questions, first run `graphify query "<question>"` when `graphify-out/graph.json` exists. Use `graphify path "<A>" "<B>"` for relationships and `graphify explain "<concept>"` for focused concepts. These return a scoped subgraph, usually much smaller than `GRAPH_REPORT.md` or raw grep output.
|
|
* Dirty `graphify-out/` files are expected after hooks or incremental updates; dirty graph files are not a reason to skip graphify. Only skip graphify if the task is about stale or incorrect graph output, or the user explicitly says not to use it.
|
|
* If `graphify-out/wiki/index.md` exists, use it for broad navigation instead of raw source browsing.
|
|
* Read `graphify-out/GRAPH_REPORT.md` only for broad architecture review or when query/path/explain do not surface enough context.
|
|
* After modifying code, run `graphify update .` to keep the graph current (AST-only, no API cost).
|
|
|
|
---
|
|
|
|
## QUICK START
|
|
|
|
The frontend `package.json` orchestrates both services. From `frontend/`:
|
|
|
|
```bash
|
|
npm install # install frontend deps
|
|
npm run dev # runs BOTH: FastAPI (:8000) + Vite dev server concurrently
|
|
```
|
|
|
|
Backend can also be run on its own. From `backend/`:
|
|
|
|
```bash
|
|
uv sync # install deps incl. dev group
|
|
uv run uvicorn app.main:app --reload --port 8000 # serve API
|
|
```
|
|
|
|
The scraper (FK login flow via Playwright) is **off by default**. To exercise it locally, use `npm run login` from `frontend/`, which sets `APP_SCRAPER_ENABLED=true APP_SCRAPER_HEADLESS=true`. Chromium must be installed separately: `uv run playwright install chromium`.
|
|
|
|
---
|
|
|
|
## REPOSITORY LAYOUT
|
|
|
|
```
|
|
backend/ Python 3.14 · FastAPI · uv (Hatchling build backend)
|
|
app/
|
|
entities/ Persistence-layer row shapes (DB-facing)
|
|
models/ Pydantic v2 request/response schemas (API-facing)
|
|
repositories/ Abstract repository interfaces
|
|
repositories_impl/
|
|
sqlite/ Default aiosqlite implementations
|
|
postgres/ Optional asyncpg implementations (extra: `postgres`)
|
|
routers/ FastAPI route handlers (thin)
|
|
services/ Business logic (planning, tips, analytics, auth, …)
|
|
scrapers/ Försäkringskassan (FK) scraper — Playwright
|
|
utils/ dates, holidays, errors, tip predicates
|
|
db/ Connection management
|
|
migrations/ Alembic migrations (NOT auto-generated from ORM models)
|
|
tests/ pytest + pytest-asyncio (asyncio_mode = auto)
|
|
|
|
frontend/ TypeScript · React 18.3 · Vite 5 · Vitest
|
|
src/
|
|
pages/ Route-level screens (Dashboard, Calendar, Settings, …)
|
|
components/ Presentational + composite components (BEM CSS classes)
|
|
hooks/ Data-fetching/state hooks (the data layer — no TanStack Query)
|
|
services/ api.ts (HTTP client) + errors.ts
|
|
context/ Auth + Workspace React context
|
|
types/ Shared TS types (api.ts, entities.ts)
|
|
utils/ dates, formatting
|
|
tests/ Vitest setup + fixtures; co-located *.test.tsx also used
|
|
|
|
.github/workflows/ CI (GitHub Actions) — backend + frontend gates
|
|
```
|
|
|
|
**Layering rule (backend):** `routers` call `services`; `services` call `repositories` (the abstract interface). Concrete DB code lives only in `repositories_impl/`. Keep routers thin and put business logic in services. Don't reach across layers (e.g. a router talking directly to `repositories_impl`).
|
|
|
|
**Data layer (frontend):** components consume `src/hooks/*`, which call `src/services/api.ts`. Components should not call `fetch`/the API client directly.
|
|
|
|
---
|
|
|
|
## CONVENTIONS
|
|
|
|
* **Backend stack:** FastAPI + Pydantic v2 (request/response models) with a **hand-rolled repository pattern** — abstract interfaces in `app/repositories/`, concrete implementations in `app/repositories_impl/{sqlite,postgres}/`. There is **no ORM** (no SQLModel, no SQLAlchemy models). SQLite via **async `aiosqlite`** is the default; Postgres via `asyncpg` is an optional extra.
|
|
* **Migrations:** Alembic (`backend/migrations/`, config `backend/alembic.ini`). Migrations are written by hand against the schema — they are not generated from ORM metadata. Add a new revision when you change the schema.
|
|
* **Auth:** password hashing with `argon2-cffi`, sessions/tokens with `PyJWT`. Rate limiting via `slowapi`.
|
|
* **Python:** `>=3.14` required. Managed via **`uv`** with the **Hatchling** build backend. Lint/format with **`ruff`** (line length 100, target `py314`). Tests with **`pytest` + `pytest-asyncio`** (`asyncio_mode = "auto"`, so no `@pytest.mark.asyncio` needed).
|
|
* **Frontend stack:** **React 18.3**, **react-router-dom v6** (`BrowserRouter`/`Routes`/`Route`), **TypeScript 5.6**, **Vite 5**, **Vitest** + Testing Library. State/data fetching is done with **custom hooks** in `src/hooks/` (no TanStack Query, no Redux).
|
|
* **Styling:** plain CSS using **BEM-style class names** (e.g. `calendar-day__number`, `calendar-day--disabled`). There is **no CSS framework** (no Tailwind) and no CSS-in-JS. Match the conventions in existing components before inventing new ones.
|
|
* **Linting:** ESLint **is** configured (flat config, `frontend/eslint.config.js`, ESLint 9). `@typescript-eslint/no-explicit-any` is an **error**. CI runs `npm run lint` with `--max-warnings 0` — zero warnings allowed.
|
|
* **Scripts:** any backend helper scripts must be placed explicitly in `backend/scripts/` (create the folder if needed). Frontend scripts go through `package.json`.
|
|
* **CI/CD:** Deployment/CI is managed via **GitHub Actions** (`.github/workflows/`). Do **not** introduce `.gitea/`, `.gitlab-ci.yml`, or `Makefile`s — extend the existing GitHub workflow instead.
|
|
|
|
---
|
|
|
|
## TESTS, LINT & CI GATES
|
|
|
|
Run the same checks CI runs before pushing.
|
|
|
|
**Backend** (from `backend/`):
|
|
|
|
```bash
|
|
uv run ruff check . # lint
|
|
uv run ruff format --check . # format check
|
|
uv run pytest # tests with coverage (config in pyproject.toml)
|
|
```
|
|
|
|
CI gate: `--cov-fail-under=80` today (current total ~84%), climbing toward **90** as tests land. Don't lower it — only raise when coverage grows.
|
|
|
|
**Frontend** (from `frontend/`):
|
|
|
|
```bash
|
|
npm run typecheck # type check (never npx tsc: it resolves a newer TS and emits a bogus TS5101)
|
|
npm run lint # ESLint, zero warnings
|
|
npm run coverage # Vitest with coverage
|
|
```
|
|
|
|
CI gates (measured baseline after `src/pages/**` inclusion):
|
|
- lines: 96 | functions: 91 | branches: 92 | statements: 96
|
|
- These act as a regression ratchet, not a target. Current coverage (97.26% lines/statements, 93.05% functions, 93.51% branches) exceeds thresholds; gates must never be lowered to pass.
|
|
|
|
### WHO RUNS THE VERIFICATION COMMANDS (cost rule)
|
|
|
|
Running the checks above is mechanical work: the commands are fixed and the only judgement
|
|
needed is reading pass/fail and the failure output. Paying a large model to sit through
|
|
`pytest`, `vitest`, `tsc`, `ruff` or a coverage run is waste.
|
|
|
|
**Rule, by who you are:**
|
|
|
|
* **Haiku subagent** — run the commands yourself, immediately. No delegation. You are already the
|
|
cheap tier; spawning another agent just adds latency.
|
|
* **Sonnet (or any larger model) subagent** — do **not** run the verification commands yourself.
|
|
Finish the code change, then spawn a **Haiku** subagent to run them and report back the raw
|
|
output. Act on what it reports.
|
|
* **The orchestrating/reviewing agent** — same rule: delegate verification runs to Haiku rather
|
|
than burning a large model on them.
|
|
|
|
This applies to the full-suite verification runs: `uv run pytest`, `uv run ruff check .`,
|
|
`uv run ruff format --check .`, `npm run typecheck`, `npm run lint`, `npm run coverage`,
|
|
`npx vitest run`, and `uv run alembic upgrade head`.
|
|
|
|
It does **not** apply to quick diagnostics while you are mid-task — reading a single file, a
|
|
`git grep`, or running one focused test to debug what you are actively writing. Delegating those
|
|
costs more than it saves. The rule targets the long, whole-suite verification passes.
|
|
|
|
**What the Haiku verifier must be told:** the exact commands, the directory to run them from, the
|
|
expected baseline (e.g. "currently 361 passed / 0 failed — must not regress"), and an instruction
|
|
to paste the **raw output verbatim**, including failures, rather than summarising. A verifier that
|
|
reports "all green" without the output is useless — several agents have reported passing gates
|
|
that were in fact failing.
|
|
|
|
### MODEL TIERS (who is allowed to be what)
|
|
|
|
Binding for all work in this repo, not just verification.
|
|
|
|
| Role | Model | Effort | Allowed to |
|
|
|---|---|---|---|
|
|
| Coordinator | **Opus** | **medium** | Dispatch tasks, review returned diffs, make product calls, maintain the plan. **Writes no implementation code.** |
|
|
| Implementer | **Sonnet** | — | Feature and bugfix work. The only tier allowed to write non-trivial code. |
|
|
| Verifier / mechanical | **Haiku** | — | Run the full suites and paste raw output; small mechanical edits (renames, i18n string moves, test-fixture churn). |
|
|
|
|
* **No subagent may run on Opus.** Not for "a hard task", not for review, not for browser
|
|
verification. If a task looks too hard for Sonnet, it is too big — split it, do not escalate it.
|
|
* The coordinator never runs the verification suites itself (see the cost rule above).
|
|
* Browser verification is delegated too — Sonnet drives Chrome, never the coordinator.
|
|
|
|
---
|
|
|
|
## ANTI-PATTERNS (CRITICAL GUARDRAILS)
|
|
|
|
* **Type Safety (TS):** No type suppression — no `as any`, `@ts-ignore`, or `@ts-expect-error`. `no-explicit-any` is an ESLint error and CI fails on warnings. **This applies to test files too** — there is no exemption. Build mock data with the typed factories in `frontend/tests/fixtures/` (`makeWorkspaceOverview`, `makePlanProposal`, `make*Result`, …), which return complete, correctly-typed objects you override with a `Partial<T>`, and mock functions with `vi.mocked(fn)` rather than casting. A cast in a test defeats the type check exactly where it guards the frontend/backend contract — which is this project's most common bug. The one accepted escape is `as unknown as T` in a test that *deliberately* exercises invalid input (a missing field, a bogus enum value); keep it to that expression and comment why.
|
|
* **Type Safety (Python):** No bare `# type: ignore` to silence the type checker; fix the underlying type or model instead. Prefer precise Pydantic models over `dict[str, Any]` at API boundaries.
|
|
* **Error Handling:** No empty `catch`/`except` blocks. Explicit handling/logging is required. Backend errors should flow through `app/utils/errors.py`; frontend errors through `src/services/errors.ts`.
|
|
* **Testing Integrity:** NEVER delete or comment out failing tests to make a suite "pass". Fix the code, or update the test properly with a clear reason.
|
|
* **No JS Slop (frontend):** NEVER create `.js`/`.jsx` files in the frontend. Use only TypeScript (`.ts`, `.tsx`).
|
|
* **Respect the layering:** routers don't talk to `repositories_impl` directly; components don't call `fetch` directly. Go through services / hooks.
|
|
* **No ORM creep:** don't add SQLModel/SQLAlchemy models — extend the existing repository pattern.
|
|
* **Assumption Error:** NEVER assume the contents of ANY file. ALWAYS read the target file to confirm its structure and implementation before modifying or referencing it.
|
|
|
|
---
|
|
|
|
## MANDATORY EXECUTION PROTOCOLS
|
|
|
|
* **Read Before Edit:** ALWAYS read the entire target file before performing any edit.
|
|
* **Post-Edit Verification:** Immediately after an edit, verify that no lines were duplicated and imports are intact.
|
|
* **Stability Check:** Following any code change, run the relevant side's checks above (lint + type check + tests). For cross-cutting changes (e.g. API contract edits in `app/models` and `src/types`), run **both** backend and frontend suites — they must agree on the wire format.
|
|
* **Keep the graph current:** after modifying code, run `graphify update .`.
|
|
|
|
---
|
|
|
|
## DOMAIN RULES (Swedish parental leave)
|
|
|
|
Useful context so calculations and UI copy stay correct:
|
|
|
|
* **390 + 90 model:** of the parental-benefit days, 390 are sickness-benefit-level and 90 are at the minimum (lägstanivå) rate. Track day *types*, not just a single total.
|
|
* **Reserved days:** a portion of days is reserved per parent and cannot be transferred to the partner — surface this in shared/partner planning.
|
|
* **Dubbeldagar (double days):** both parents can take leave on the same day for a limited number of days early in the child's life; model these as consuming each parent's balance.
|
|
* **No partial days:** the planner works in **whole days** — do not introduce fractional/percentage days in the model.
|
|
* **The age-4 cutoff:** the core purpose is optimizing leave over time. After the child turns **4**, only **96 days** may be saved; the planner must show how many days remain at that boundary.
|
|
* **Money/SGI is abstracted:** the app treats benefit amounts/SGI abstractly rather than computing exact kronor payouts — keep money handling at the agreed abstraction level rather than hardcoding rates.
|
|
|
|
When in doubt about a rule, ask before encoding a number — Swedish parental-leave rules change and incorrect figures are worse than a TODO.
|
|
|
|
---
|
|
|
|
## WHERE TO LOOK FIRST
|
|
|
|
* `ARCHITECTURE.md` — system design and rationale.
|
|
* `graphify-out/wiki/index.md` — generated navigation over the codebase.
|
|
* `backend/README.md` — backend-specific setup notes.
|
|
* The various `*_REPORT.md` / `*_TODO.md` / `*_ONBOARDING.md` files at the root are working handoff notes between contributors; treat them as **historical context**, not authoritative spec.
|
|
|
|
---
|
|
|
|
## OUTPUTS
|
|
|
|
Be concise: skip preamble, caveats, and restating the request. Use sentence fragments and short bullet points where clarity isn't lost. Explain reasoning only when it aids debugging or isn't obvious from the code/diff itself.
|