core: чанкер markdown по заголовкам с перекрытием

This commit is contained in:
2026-10-02 01:14:00 +03:00
parent d62a26f5b1
commit de3cb24edc
4 changed files with 196 additions and 1 deletions
+1 -1
View File
@@ -31,7 +31,7 @@ fun chunkMarkdown(text: String): List<Chunk>
## ChunkerTest.kt — ровно 5 тестов, имена ровно такие
1. `splitsByHeadingLevels` — вход с '# A', '## B', '### C' → 3 чанка, heading = ["A","B","C"], line = [1,3,5], ord = [0,1,2].
2. `textBeforeFirstHeadingBecomesChunkWithEmptyHeading` — ввод «преамбула\n# Заголовок\nтело» → 2 чанка, первый: heading "»", line 1; второй heading "Заголовок".
2. `textBeforeFirstHeadingBecomesChunkWithEmptyHeading` — ввод "преамбула\n# Заголовок\nтело" → 2 чанка, первый: heading = "" (пустая строка), line 1; второй heading "Заголовок".
3. `fourHashesIsNotAHeading` — строка '#### не заголовок' остаётся частью текста предыдущего раздела.
4. `emptySectionsAreDropped` — ввод с двумя заголовками подряд без текста между ними → 1 чанк.
5. `longSectionIsSplitWithOverlap` — раздел из 6000 символов 'а' → 2 чанка, длина первого 4000,
+57
View File
@@ -0,0 +1,57 @@
Проект: /root/WORK/memo (Kotlin/JVM). Заказ по индексации markdown-файлов в базу.
Создать РОВНО ОДИН новый файл: memo-core/src/main/kotlin/memo/core/Indexer.kt
Дополнить существующий: memo-core/src/test/kotlin/memo/core/CoreSmokeTest.kt (добавить ОДИН новый тест-метод в конец класса, существующие не трогать)
## Indexer.kt
package memo.core
class Indexer(private val db: Db, private val embedder: Embedder) {
/** Индексирует один файл. Возвращает true, если файл был (пере)проиндексирован,
* false — если пропущен (не изменился с прошлой индексации). */
fun indexFile(path: java.io.File): Boolean
/** Индексирует все .md файлы в дереве каталога root (рекурсивно). Возвращает число
* реально переиндексированных файлов. */
fun indexTree(root: java.io.File): Int
}
Правила:
1. Перед индексацией файла сравнить с записью в таблице files по колонке path:
- если записи нет — индексировать;
- если mtime и size совпадают с сохранёнными — вернуть false, ничего не делать (НЕ считать хэш);
- если mtime/size изменились — посчитать sha-256 содержимого; если хэш совпал с сохранённым —
обновить в files поля mtime/size/indexed_at (файл тронули, содержимое то же) и вернуть false;
- иначе — полная переиндексация файла.
2. Полная переиндексация файла: удалить из chunks все строки с этим path; удалить из chunks_fts строки
с rowid из удалённых chunks (DELETE FROM chunks_fts WHERE rowid IN (...)); то же для chunks_vec.
Затем разбить текст через chunkMarkdown (см. memo-core/src/main/kotlin/memo/core/Chunker.kt),
для каждого чанка: посчитать эмбеддинг embedder.embed(chunk.text), вставить строку в chunks,
взять last_insert_rowid() и вставить те же rowid в chunks_fts (колонки text, heading)
и в chunks_vec (колонка embedding). Всё — в одной транзакции (conn.beginTransaction/commit/rollback).
3. В files писать: path (абсолютный), mtime (доля секунды, REAL), size, hash (hex sha-256), indexed_at (epoch секунды).
4. indexTree: обход рекурсивно, только файлы с расширением .md, скрытые каталоги (начинающиеся с '.')
и каталоги .memo не обходить. Возвращать число переиндексированных файлов.
## Тест: добавить в CoreSmokeTest.kt метод
fun indexerSkipsUnchangedFile()
- создать временный каталог, в нём файл note.md с содержимым "# Заголовок\nтело заметки\n## Второй\nещё текст";
- создать Db на временном index.db (не забыть db.init()) и Embedder на модели
(путь брать так же, как в существующем тесте embedderProduces768 — через MEMO_MODEL_DIR);
- первый indexFile(...) должен вернуть true;
- второй indexFile(...) сразу же — обязан вернуть false (файл не менялся);
- дописать в файл "\n## Третий\nновый текст\n", второй indexFile обязан вернуть true;
- в конце: assertTrue, что SELECT count(*) FROM chunks без условия больше нуля (запрос через db.conn).
После: ./gradlew :memo-core:test --rerun-tasks — все тесты зелёные.
Коммит: git add -A && git commit -m "core: индексатор файлов с пропуском неизменённых"
СТРОГИЕ ЗАПРЕТЫ:
- Не выводить план текстом; сразу создавай/правь файлы.
- Не трогать Chunker.kt, Embedder.kt, Db.kt, build.gradle.kts и любые другие файлы.
- Не менять существующие тесты.
- Не добавлять зависимости.