# Phase 02 — Story: Import Documents **Story:** `.agent/user_stories/import-documents.md` **Context:** `.agent/PLAN.md` §5 (data model), §9 (logging), §11 (import workflow) ## Goal The importer (`scripts/import_docs.py`) + `GET /api/docs` + the Sources page rendering the indexed documents — the knowledge base becomes refreshable. ## Implementation steps 1. `app/rag/__init__.py`, `app/rag/chunker.py` — markdown-aware chunker (PLAN §5 policy: heading splits, 2000-char target, 200 overlap, keep nearest heading). Pure functions, fully unit-testable. 2. `app/rag/llm.py` — `LLMClient` (openai async) with `embed(texts) -> list[list[float]]` (batched, `BOR_EMBED_BATCH_SIZE`) and a `embed_one`; dimension check vs `settings.embedding_dim` with a loud, actionable error. (Chat streaming is added in Phase 03 on this client.) 3. `app/rag/importer.py` — the core: directory walk (exclusion list, PLAN A9; `*.md` only), sha256 delta vs `documents.content_hash`, upsert-or-skip, two-phase chunk replace (insert doc → replace chunks → embed → commit), `--prune` support, per-file + summary logging. 4. `scripts/import_docs.py` — CLI wrapper (argparse): repeatable `--source` (default `~/Homelab` `~/Deployments`, `expanduser`), `--prune`, `--limit`. 5. `app/api/docs.py` — `GET /api/docs` → `{"documents": [DocSummary]}` (include `chunks` count via `func.count`); mount in `app/main.py` **before** the static mount. 6. `frontend/assets/sources.js` + `sources.html` polish — wire the real endpoint (already scaffolded to expect this shape); keep the empty state. 7. Update `README.md` §Knowledge Base Import with the final commands + exclusion list + "update your docs → re-run the script" workflow. ## UI Verification Compare `/sources.html` against the story's "UI Visualization & Structure": stat cards `auto-fit minmax(170px,1fr)`; full-width table (≥85% container); mono path column with `title` ellipsis; empty state with the exact command; `