--- pr: 75 title: Rename to memory, OpenAI env, chunking, batching, dedup --- ## Что сделано - `pyproject.toml`: `name = "second-brain"` → `name = "memory"`, добавлена секция `[project.scripts] rag = "src.memory.cli:main"`. `uv.lock` регенерирован (`uv lock`). - `src/memory/cli.py`: `prog="rag"` → `prog="memory"`. - `src/memory/embedder.py`: env нейминг `AI_PROVIDER_API_URL`/`AI_PROVIDER_API_KEY` → `OPENAI_BASE_URL`/`OPENAI_API_KEY` (OpenAI-совместимый). `API_URL.rstrip("/")` фиксит trailing-slash баг (`/v1//embeddings` → 404). `EMBEDDING_MODEL` из env `OPENAI_EMBEDDING_MODEL` (default `gemini-embedding-2-preview`). `BATCH_SIZE = int(os.environ.get("OPENAI_EMBEDDING_BATCH_SIZE", "2048"))` — `embed_texts` режет texts > BATCH_SIZE на батчи, конкатенирует результаты. - `src/memory/index.py`: добавлена `_chunk_text(text, size, overlap) -> list[tuple[str, int]]` (graceful при size<=overlap → 1 chunk). `run_index` использует чанки (`MEMORY_CHUNK_SIZE` default 512, `MEMORY_CHUNK_OVERLAP` default 64 из env). `file_map` поля: `source`, `chunk_idx`, `offset`, `text`. Пустые файлы → 1 запись с пустым чанком (не дропаются). - `src/memory/search.py`: дедуп по `source` в top-K — после sort по score, итерация с `seen` set, оставляет highest score per source, останавливается на K уникальных. - `.env.example`: добавлен блок "OpenAI Embeddings (Memory CLI)" с 6 переменными (`OPENAI_BASE_URL`, `OPENAI_API_KEY`, `OPENAI_EMBEDDING_MODEL`, `OPENAI_EMBEDDING_BATCH_SIZE`, `MEMORY_CHUNK_SIZE`, `MEMORY_CHUNK_OVERLAP`). `AI_PROVIDER_BASE_URL`/`AI_PROVIDER_API_KEY` оставлены (LLM провайдер в opencode.json, отдельный concern). - `tests/test_embedder.py`: env нейминг (`OPENAI_BASE_URL`), + 4 теста: `test_embed_texts_default_model`, `test_embed_texts_custom_model` (reload с env override), `test_embed_texts_trailing_slash` (URL без `//`), `test_embed_texts_batches` (3000 texts → 2 вызова [2048, 952]). - `tests/test_index.py`: + 3 теста: `test_chunking_long_file` (1500 chars → 3+ records, chunk_idx/offset), `test_chunking_size_env_override` (`MEMORY_CHUNK_SIZE=256`), `test_chunking_skip_rag_dir`. Существующий `test_index_creates_json` обновлён (проверка `chunk_idx`/`offset`). - `tests/test_search.py`: + `test_dedup_by_source` (2 чанка одного source → 1 в top-K, highest score). - `tests/test_chunking.py` (новый): 7 edge cases `_chunk_text` — empty, 1 char, ровно size, size+1, unicode emoji, size