# CLAUDE.md — Library Desk FastAPI service that ingests Wiki.js content and serves retrieval over it. Talks to Postgres (Wiki.js DB + `LISTEN/NOTIFY`), Qdrant (vectors), Neo4j (graph), Ollama (embeddings) and Redis (job manager). Deployed on tower-of-joy at **:8089**. ## Ports — these differ, deliberately | | Port | How | |---|---|---| | Local dev | **8778** | `./wakeup.sh`, uvicorn reload mode, logs to `logs/server.log` | | Production | **8089** | container; health at `http://192.168.86.149:8089/health` | Testing `localhost:8089` on the dev box hits the *container*, not your reload server. ## Work tracking Work lives in **pql**, not a markdown file. `TODO.md` was migrated and removed 2026-08-08. ```bash pql ticket list # open work pql plan whatsnext # next unblocked item, with context pql ticket show T-1 # detail pql ticket new bug "title" # file new work ``` Tickets travel with the repo — `.pql/changelog/` is committed and replayed by the git hooks; the databases are ignored and rebuildable with `pql plan rebuild`. Do not add a TODO section to a markdown file. ## Known live defect **Every wiki page change is ingested twice** (T-1, proven against v1.9.1). The Dockerfile runs `uvicorn --workers 2`, `startup_event` creates a `WikiChangeListener` per worker, and Postgres delivers `NOTIFY` to *every* listening session. The debounce in `wiki_change_listener.py` is an in-process dict and cannot dedupe across workers. Relevant when touching ingestion: duplicate work reaches Ollama, Qdrant and Neo4j. Whether it merely wastes GPU or actually corrupts state is **not yet established** — check before assuming either. ## Working here **Plan, act, reflect.** Outline which files you will touch and the side effects before writing. Change in small atomic steps. Afterwards, verify: did existing tests break, and does the new behaviour have a test? **Test locally first — the build-deploy loop is slow.** `./wakeup.sh` auto-reloads on code changes (not on `requirements.txt` changes). Deploy only when a feature is complete and tested. Run tests through the venv explicitly, to avoid environment mismatch: ```bash .venv/bin/python -m pytest tests/ ``` Copy `.env.example` to `.env` and configure Ollama, Redis, Neo4j, Qdrant and Wiki.js hosts. ## Git - **History is linear — no merge commits.** Work on `main`, or a short-lived branch that is fast-forwarded and deleted. (A feature-branch mandate was retired 2026-08-08 to match the workspace convention, which now has no per-repo exceptions.) - **Conventional Commits**: `feat:`, `fix:`, `refactor:`, `docs:`, `chore:`. - **Atomic commits** — one logical change each. - **Stage explicitly. Never `git add -A`** — it is denied by policy, and it sweeps in whatever else is dirty, including secrets. - Update `CHANGELOG.md` with every user-facing change, under `[Unreleased]` in `Added` / `Changed` / `Fixed`. ## Releasing Ask whether a deploy is wanted first — it is not automatic. 1. Bump the version in `pyproject.toml` (patch for fixes, minor for features). 2. Move `[Unreleased]` entries into a dated version section in `CHANGELOG.md`. 3. Stage the changed files by name, commit, tag `vX.Y.Z`, `git push origin main --tags`. 4. Gitea CI builds the image on the tag; Watchtower deploys it. 5. Verify: `curl http://192.168.86.149:8089/health`. ## Architecture Group by **domain, not by file type**. A single large `routers/` folder is the thing to avoid. ```text src/ ├── auth/ │ ├── router.py # endpoints │ ├── schemas.py # pydantic models │ ├── service.py # business logic │ ├── dependencies.py # module-scoped dependencies │ └── config.py # module-scoped settings └── main.py # app entry point ``` Reference: [FastAPI best practices](https://github.com/zhanymkanov/fastapi-best-practices). The live contract is always `http://localhost:8089/openapi.json` (70 paths) — generated from running code, so it cannot drift the way this file can.