One agent doc per repo, and it is CLAUDE.md. Two agent docs drift, and
the one nobody read is always the one holding the rule that mattered —
this repo had a CLAUDE.md whose entire content was an instruction to go
read the other file.
Composed fresh rather than reformatted. Everything factual carries over;
the structure follows what someone working here actually needs first.
Three corrections made while carrying content across:
- The release flow instructed `git add -A`. That is denied by policy
and sweeps in whatever else is dirty, including secrets. Now: stage
by name.
- The feature-branch mandate is gone. Linear history everywhere, no
per-repo exceptions as of 2026-08-08.
- The 8778/8089 split is stated explicitly rather than left implicit
in two separate sections. 8778 is wakeup.sh's reload server, 8089 is
the container — testing the wrong one silently exercises the wrong
build. Both ports verified against wakeup.sh and the deployed stack
before writing them down.
Adds the live double-ingestion defect (T-1) where someone touching
ingestion will see it, including the part that is not yet established:
whether it merely wastes GPU or actually corrupts Qdrant and Neo4j.
Co-Authored-By: Claude <noreply@anthropic.com>
4.0 KiB
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.
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:
.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.mdwith every user-facing change, under[Unreleased]inAdded/Changed/Fixed.
Releasing
Ask whether a deploy is wanted first — it is not automatic.
- Bump the version in
pyproject.toml(patch for fixes, minor for features). - Move
[Unreleased]entries into a dated version section inCHANGELOG.md. - Stage the changed files by name, commit, tag
vX.Y.Z,git push origin main --tags. - Gitea CI builds the image on the tag; Watchtower deploys it.
- 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.
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.
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.