# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Project Overview Zotero KB is a local FastAPI web service that reads from a Zotero SQLite database and generates structured Markdown knowledge cards for academic writing workflows. It provides project management, collection browsing, batch card generation via LLM (DeepSeek), and citation recommendation APIs. ## Development Commands Package manager is `uv`. All commands run through `uv run`. ```bash # Install dependencies UV_CACHE_DIR=/tmp/uv-cache uv sync --extra dev # Run the server UV_CACHE_DIR=/tmp/uv-cache uv run python main.py # Serves on http://127.0.0.1:8000 # Run all tests UV_CACHE_DIR=/tmp/uv-cache uv run pytest -q # Run a single test file UV_CACHE_DIR=/tmp/uv-cache uv run pytest tests/test_api.py -q # Run a single test UV_CACHE_DIR=/tmp/uv-cache uv run pytest tests/test_api.py::test_create_project_endpoint -q ``` ## Environment Variables | Variable | Default | Purpose | |----------|---------|---------| | `ZOTERO_DATA_DIR` | `/mnt/c/Users/WSX/Zotero` | Path to Zotero data directory (must contain `zotero.sqlite`) | | `ZOTERO_KB_WORKSPACE` | `workspace` | Workspace directory for projects, indexes, and cards | | `ZOTERO_BRIDGE_FILE` | `workspace/bridge/selected-items.json` | Bridge file for Zotero-to-project item import | | `DEEPSEEK_API_KEY` | — | Required when projects use `llm_provider=deepseek` | ## Architecture ### Layer Overview `api.py` → FastAPI routes → `Workspace` / `ProjectService` / `WritingService` / `ZoteroReader` / `CardBuilder` All business logic lives in `src/zotero_kb/`. There is no database migration system; the app creates directories on demand. ### ZoteroReader (`zotero_reader.py`) Reads directly from `zotero.sqlite` via `sqlite3`. Key capabilities: - `read_items(item_keys)` — full record with metadata, creators, tags, notes, attachment texts - `get_collection_tree()` / `get_collection_items(collection_key)` — browse collections hierarchically - `search_items(query)` — title/abstract substring search - PDF text extraction delegates to `pdftotext` CLI; other attachments read as plain text - Attachment paths resolve from `storage:` relative to `ZOTERO_DATA_DIR/storage/` ### Workspace & Project Model (`workspace.py`, `projects.py`) A **project** is a JSON file plus two companion files under `workspace/projects//`: - `project.json` — metadata including `llm.provider`, `llm.model`, `card_language` - `selected-items.json` — ordered list of Zotero item keys belonging to the project - `project-index.json` — cached denormalized view rebuilt on every read `Workspace` creates/renames/deletes projects. `ProjectService` manages item selection and rebuilds `project-index.json` by reading three shared indexes: - `workspace/library/index/items.json` — item metadata - `workspace/library/index/cards.json` — generated card data (supports per-language variants) - `workspace/library/index/collections.json` — collection metadata ### Card Generation (`cards.py`, `llm.py`) `CardBuilder.build_or_update(item, language)`: 1. Builds a **source bundle** from the item record (metadata + notes + attachment texts) 2. Hashes and caches the bundle to `library/cache/source-bundles/` 3. Calls `llm_client.generate_card(source_bundle)` 4. Renders a Markdown file to `library/collections// [<key>][<lang>].md` 5. Updates `items.json`, `cards.json`, and `collections.json` The LLM layer supports two providers: - `deepseek` — calls DeepSeek Chat API, requires `DEEPSEEK_API_KEY` - `deterministic` (fallback) — derives card fields from abstract/notes without any API call Cards support **language variants**: `cards.json` stores a map `item_key -> {language -> card_data}`. The `project-index.json` selects the variant matching the project's `card_language`. ### Writing Support (`writing.py`) - `recommend_citations(project_id, prompt)` — simple term-overlap scoring across project cards - `generate_plan(project_id, prompt)` — returns a single-section plan using the top-scored card ### Bridge Import (`bridge.py`) The `ZOTERO_BRIDGE_FILE` (JSON with `selected_keys` array) is read by the `/api/projects/{id}/imports/selected-items` endpoint to import items that were pre-selected in Zotero. This is separate from the UI-driven `/imports/item-keys` endpoint. ### Frontend A single `index.html` (Jinja2 template rendered as static HTML) provides the full UI. It uses vanilla JS to call the REST API. No build step or JS bundler. ## Testing Tests use `fastapi.testclient.TestClient` with a `FakeLlmClient` injected via `create_app(config, llm_client=...)`. The fixture builder (`tests/fixtures/build_zotero_fixture.py`) creates an in-memory Zotero SQLite schema with sample data. Most tests extract route handlers directly from the FastAPI app rather than using HTTP-level client calls. ## Workspace Directory Layout ``` workspace/ bridge/ selected-items.json # bridge file from Zotero library/ index/ items.json # item metadata index cards.json # card data index (language variants) collections.json # collection metadata index collections/ <collection_path>/ <title> [<key>][<lang>].md # generated Markdown cards cache/ source-bundles/ <key>.<lang>.json # cached source bundles projects/ <project_id>/ project.json # project config selected-items.json # item keys in this project project-index.json # denormalized project view ``` ## Related Subdirectories - `zotcard/` — Zotero plugin (separate JS project, not part of the Python service) - `zotero-rag/` — Standalone RAG search service (separate project) - `zotero-bridge/` — Zotero plugin bridge component - `skills/zotero-citation-planner/` — Claude skill for reading project indexes and generating citation plans