Imports:
  - Types:
      - Storage
    Usages:
      - reading
    From: sententia/storage

Usages:
  conventions: .goga/usages/conventions.md
  faiss: .goga/usages/cooks/faiss.md
  sentence_transformers: .goga/usages/cooks/sentence_transformers.md
  text_splitting: .goga/usages/cooks/text_splitting.md

Annotations: |
  Использовать `conventions` для правил написания кода и тестов.

  Клетка отвечает за индексацию Markdown-файлов и поиск релевантных фрагментов.
  Поддерживает два режима работы:
  - Персистентный: при заданном index_path индекс загружается/сохраняется в файл
  - In-memory: при index_path=None индекс строится с нуля при каждом запуске, не сохраняется на диск

  Использовать практику `faiss` для векторного хранилища.
  Использовать практику `sentence_transformers` для генерации эмбеддингов.
  Использовать практику `text_splitting` для разбиения текста на чанки.

  Порядок индексации: получение файлов через `reading` → очистка → chunking → эмбеддинги → FAISS.
  Нормализовать векторы перед добавлением в FAISS.

---

"Index(storage: Storage, index_path: str | None = None, default_top: int = 10)":
  location: indexer.py
  annotations: |
    Индекс Markdown-файлов с семантическим поиском.
    Поддерживает два режима работы:
    - При заданном index_path: проверяет наличие сохранённого индекса.
      Если файл существует — загружает через load. Если нет — строит через `storage` и сохраняет через save.
    - При index_path=None: строит индекс с нуля через `storage`. Индекс существует только в памяти.
      Методы save и load недоступны — выбрасывают ошибку при вызове.

    `storage`: экземпляр Storage для доступа к Markdown-файлам.
    `index_path`: путь к файлу для сохранения/загрузки FAISS индекса. Если None — in-memory режим.
      Пустая строка недопустима — выбрасывает ValueError.
    `default_top`: количество результатов поиска по умолчанию.

    Модель эмбеддингов — внутренняя деталь. Чанки получают префикс "passage: ", запросы — "query: "
    (требование модели E5).

    Использовать практику `faiss` для векторного хранилища.
    Использовать практику `sentence_transformers` для генерации эмбеддингов.
    Использовать практику `text_splitting` для разбиения текста на чанки.
    Использовать практику `reading` для работы с файлами.
  methods:
    "index_directory() -> index_info:dict": |
      Построение индекса из файлов Storage.

      Алгоритм:
      1. Получение списка файлов и чтение содержимого каждого.
      2. Очистка текста от Markdown-разметки (блоки кода, изображения, ссылки, заголовки).
      3. Разбиение очищенного текста на чанки с перекрытием.
      4. Добавление префикса "passage: " к каждому чанку (требование модели E5).
      5. Генерация эмбеддингов и нормализация векторов.
      6. Создание FAISS-индекса (IndexFlatIP) и добавление векторов.

      `index_info`: словарь {"files": int, "chunks": int, "dimensions": int} —
      количество файлов, чанков и размерность векторов.

      Требования:
      - Векторы нормализуются перед добавлением в FAISS.
      - Если файлов нет — создаётся пустой индекс с размерностью, определённой пробным эмбеддингом.

      Использовать практику `reading` для работы с файлами.
      Использовать практику `faiss` для создания и заполнения индекса.
      Использовать практику `sentence_transformers` для генерации эмбеддингов.
      Использовать практику `text_splitting` для разбиения текста.

    "search(query: str, top: int | None = None) -> results:list": |
      Векторизация запроса и поиск top-N релевантных чанков в FAISS.
      Если `top` не указан — используется default_top.

      `query`: текст поискового запроса. Векторизуется с префиксом "query: ".
      `top`: количество возвращаемых результатов.

      `results`: список объектов {"text": str, "source": str, "score": float},
                 отсортированных по убыванию релевантности.

      Использовать практику `faiss` для поиска.
      Использовать практику `sentence_transformers` для векторизации запроса.

    "save() -> void:none": |
      Сохранить FAISS индекс и метаданные на диск в index_path.
      Выбрасывает ошибку, если index_path равен None.

      Использовать практику `faiss` для сериализации индекса.

    "load() -> loaded:bool": |
      Загрузить FAISS индекс и метаданные с диска из index_path.
      Выбрасывает ошибку, если index_path равен None.

      `loaded`: True если загрузка успешна, False если файл не найден
                или формат повреждён.

      Использовать практику `faiss` для десериализации индекса.

---

Author: Goga
CreatedAt: 07/05/26
Description: |
  Индексация Markdown-файлов и семантический поиск на основе FAISS + Sentence-Transformers.
