# CLAUDE.md — working agreement for AI agents on Siftlode This repository is **fully AI-authored** — every line was written by an AI agent, and it continues to be developed that way. This file is the rules of the road: read it before you touch anything. It is loaded into every agent session, so keep it **short and true**. A rule here that has gone stale is worse than no rule — if you change how the code works, change this file in the same commit. Siftlode is a self-hosted YouTube reader (+ a local Plex browser/player and a download center). It is a **modular monolith**: one Postgres, one FastAPI process (uvicorn + APScheduler), one worker process (yt-dlp/ffmpeg), one PO-token sidecar. React 18 + react-query frontend with a router-free navigation provider. ## The gate — never merge or ship red The local gate is **ruff + typecheck (3 tsc projects) + eslint + prettier --check + knip + vitest + pytest**. It runs on every `publish`, which **refuses to build on failure — there is deliberately no `--skip` flag.** Run it and get it green **before** any merge, release, or handing work to review. - Full gate (agents with the private ops dispatcher): `siftlode check`. - Directly, without the dispatcher: `cd frontend && npm run typecheck && npm run lint && npm run knip && npm run test`, plus backend `ruff check backend` and `pytest` (the DB-lane needs Postgres). - New DB-backed test? It must actually run, not silently skip — assume `SIFTLODE_TEST_REQUIRE_DB=1`. ## Workflow - **Trunk model.** One long-lived branch, `main`. Branch off it (`feature/`, `bug/`, `improvement/`), keep commits logical and Conventional-Commit-styled. - **No AI attribution in commits.** No `Co-Authored-By` trailer, no "generated by" lines. (This file being AI-written is the intended, visible exception; commits are not.) - **Release is a deliberate, user-owned step** (`siftlode release` from the feature branch: merge to `main` → publish → deploy → verify `/api/version`). Do not self-ship; bring work to a green, committed state and stop for the user's go-ahead. - **Public repo.** Keep ops/topology/secrets out — those live in a separate private repo. Nothing here should assume or leak them. ## Architecture shape (don't fight it) `routes/*` (HTTP only: validation, auth-dependency, serialization) → domain packages `sync/`, `plex/`, `downloads/`, `youtube/` (shared by routes + scheduler + worker) → flat core (`auth`, `quota`, `sysconfig`, `state`, `audit`, `notifications`, `userscope`, `realtime`, `ratelimit`, `progress`, `security`) → `models/db/config`. **Dependency direction is one-way: core never imports routes.** Frontend: `main.tsx` provider tree → `App.tsx` shell → `moduleRegistry` route-chunks → components; data via react-query (`qk` factory), per-account `localStorage`, and server prefs; API through `lib/api/*` slices over `core.req`. **Not wanted** (reviewed and rejected as over-engineering for this size): microservices, event bus, CQRS, a repository/DI framework, a router library, a generic CRUD-router factory or base serializer. ## Canonical patterns — when you have a choice, pick THIS | Area | Use | Not | |---|---|---| | ORM | SQLAlchemy 2.0 `select()` | `db.query(...)` | | Request body | a Pydantic model | `payload: dict` | | Ownership check | `owned_or_404` / `get_or_404` | ad-hoc `_own_playlist`/`_owned`/`_own_job` | | Admin gate import | `app.auth` | the `app.routes.admin` re-export | | Upstream failure status | `422` with JSON detail | `502` (reserve 502 for raw byte-proxies) | | User-facing error | structured `detail` (`{code, reason}`) | a bare English string under a bilingual UI | | Mutation response | `{"ok": true, …}` (`{"deleted": id}`) | ad-hoc `{"unsubscribed"}`/`{"cleared": n}`/echo | | Transaction commit | the handler/job-wrapper commits; helpers `flush` | helpers that commit (only `quota` owns its session) | | Admin knob storage | `sysconfig` (values) | env / `app_state` / `scheduler_settings` scatter | | Job status word | the `downloads/status.py` StrEnum | loose `"canceled"`/`"cancelled"` literals | | Current time | inline `datetime.now(timezone.utc)` | new `now_utc`/`_now` helpers | | FE per-account state | `usePref` | raw `readAccount`/`writeAccount` or bespoke hooks | | FE control | the chip/segmented primitives (`Select` only for >6 options) | a native `