# AGENTS.md Personal health & skincare data hub with LLM agent integration. Monorepo: Python FastAPI backend + SvelteKit frontend. ## Structure ``` innercontext/ ├── backend/ # Python 3.12, FastAPI, SQLModel, PostgreSQL, Gemini │ ├── innercontext/ # Main package │ │ ├── api/ # 7 FastAPI routers + LLM endpoints │ │ ├── models/ # SQLModel tables + Pydantic types (12 files) │ │ ├── validators/# LLM response validators (6 validators) │ │ ├── services/ # FX rates (NBP API), pricing jobs │ │ └── workers/ # Background pricing worker │ ├── tests/ # pytest (171 tests, SQLite in-memory) │ ├── alembic/ # DB migrations (17 versions) │ ├── main.py # App entry, lifespan, CORS, router registration │ └── db.py # Engine, get_session(), create_db_and_tables() ├── frontend/ # SvelteKit 2, Svelte 5, Tailwind v4, bits-ui │ └── src/ │ ├── routes/ # File-based routing (15+ pages) │ ├── lib/ # API client, types, components, i18n │ └── app.css # Theme + editorial design system ├── docs/ # Deployment guides + frontend-design-cookbook.md ├── nginx/ # Reverse proxy (strips /api prefix → backend:8000) ├── systemd/ # 3 units: backend, frontend-node, pricing-worker ├── scripts/ # Health checks, backups, env validation └── deploy.sh # Push-based deploy (Capistrano-style symlinked releases) ``` ## Agent Skills - `svelte-code-writer`: REQUIRED for `.svelte`, `.svelte.ts`, `.svelte.js` files. - `frontend-design`: Frontend UI, page, and component design work. - `conventional-commit`: Commit messages following Conventional Commits. - `gemini-api-dev`: Gemini API integrations, multimodal, function calling, structured output. When editing frontend code, follow `docs/frontend-design-cookbook.md` and update it when introducing or modifying reusable UI patterns, visual rules, or shared styling conventions. ## Where to Look | Task | Location | Notes | |------|----------|-------| | Add API endpoint | `backend/innercontext/api/` | Follow router pattern, use `get_or_404()` | | Add/modify model | `backend/innercontext/models/` | See `backend/AGENTS.md` for JSON col + timestamp conventions | | Add DB migration | `backend/alembic/` | `cd backend && uv run alembic revision --autogenerate -m "desc"` | | Add frontend page | `frontend/src/routes/` | `+page.svelte` + `+page.server.ts` (load + actions) | | Add component | `frontend/src/lib/components/` | Use bits-ui primitives, check design cookbook | | Add LLM feature | `backend/innercontext/api/` + `llm.py` | `call_gemini()` or `call_gemini_with_function_tools()` | | Add LLM validator | `backend/innercontext/validators/` | Extend `BaseValidator`, return `ValidationResult` | | Add i18n strings | `frontend/messages/{en,pl}.json` | Auto-generates to `src/lib/paraglide/` | | Modify design system | `frontend/src/app.css` + `docs/frontend-design-cookbook.md` | Update both in same change | | Modify types | `backend/innercontext/models/` → `pnpm generate:api` → `frontend/src/lib/types.ts` | Auto-generated from OpenAPI; bridge file may need augmentation | ## Commands ```bash # Backend cd backend && uv run python main.py # Start API server cd backend && uv run ruff check . # Lint cd backend && uv run black . # Format cd backend && uv run isort . # Sort imports cd backend && uv run pytest # Run tests # Frontend cd frontend && pnpm dev # Dev server (API proxied to :8000) cd frontend && pnpm check # Type check + Svelte validation cd frontend && pnpm lint # ESLint cd frontend && pnpm format # Prettier cd frontend && pnpm build # Production build → build/ cd frontend && pnpm generate:api # Regenerate types from backend OpenAPI ``` ## Commit Guidelines Conventional Commits: `feat(api): ...`, `fix(frontend): ...`, `test(models): ...`. Include scope indicating which part of the monorepo is affected. ## Architecture **Backend:** Python 3.12, FastAPI, SQLModel 0.0.37 + SQLAlchemy, Pydantic v2, PostgreSQL (psycopg3), Gemini API (google-genai). **Frontend:** SvelteKit 2, Svelte 5 (Runes), TypeScript, Tailwind CSS v4, bits-ui (shadcn-svelte), Paraglide (i18n), svelte-dnd-action, adapter-node. ### Cross-Cutting Patterns - **Type sharing**: Auto-generated from backend OpenAPI schema via `@hey-api/openapi-ts`. Run `cd frontend && pnpm generate:api` after backend model changes. `src/lib/types.ts` is a bridge file with re-exports, renames, and `Require<>` augmentations. See `frontend/AGENTS.md` § Type Generation. - **API proxy**: Frontend server-side uses `PUBLIC_API_BASE` (http://localhost:8000). Browser uses `/api` (nginx strips prefix → backend). - **Auth**: None. Single-user personal system. - **Error flow**: Backend `HTTPException(detail=...)` → Frontend catches `.detail` field → `FlashMessages` or `StructuredErrorDisplay`. - **LLM validation errors**: Non-blocking (HTTP 200). Returned in `validation_warnings` field. Frontend parses semicolon-separated strings into list. ### Models | File | Tables | |------|--------| | `product.py` | `products`, `product_inventory` | | `health.py` | `medication_entries`, `medication_usages`, `lab_results` | | `routine.py` | `routines`, `routine_steps`, `grooming_schedules` | | `skincare.py` | `skin_condition_snapshots` | | `profile.py` | `user_profiles` | | `pricing.py` | `pricing_recalc_jobs` | | `ai_log.py` | `ai_call_logs` | **Product** is the core model with JSON columns for `inci`, `actives`, `recommended_for`, `targets`, `product_effect_profile`, and `context_rules`. `to_llm_context()` returns a token-optimised dict for LLM usage. ### Deployment - **CI**: Forgejo (`.forgejo/workflows/`), manual trigger only. - **Deploy**: `deploy.sh` pushes via SSH to LXC host. Capistrano-style timestamped releases with `current` symlink. Auto-rollback on health check failure. - **Services**: 3 systemd units — backend (uvicorn :8000), frontend-node (:3000), pricing-worker. - **Env**: Backend `.env` has `DATABASE_URL` + `GEMINI_API_KEY`. Frontend `PUBLIC_API_BASE` set at build time. ## Anti-Patterns (this project) - `model_validator(mode="after")` does NOT fire on `table=True` SQLModel instances (SQLModel 0.0.37 + Pydantic v2 bug). Validators in Product are documentation only. - Never use plain `Field(default_factory=...)` for `updated_at` — must use `sa_column=Column(DateTime(timezone=True), onupdate=utc_now)`. - JSON columns use `sa_column=Column(JSON, nullable=...)` — NOT JSONB. DB-agnostic. - Gemini API rejects int-enum in `response_schema` — `AIActiveIngredient` overrides with `int` + `# type: ignore[assignment]`. - `backend/skincare.yaml` is legacy notes — ignore, not part of data model. - ESLint rule `svelte/no-navigation-without-resolve` has `ignoreGoto: true` workaround (upstream bug sveltejs/eslint-plugin-svelte#1327). - `_ev()` helper in `product.py` normalises enum values when fields may be raw dicts (from DB) or Python enum instances. - No frontend tests exist. Backend tests use SQLite in-memory (not PostgreSQL).