Проект: /root/WORK/memo (Kotlin/JVM). Заказ на ИСПРАВЛЕНИЕ дефекта определения коллекций. ## Доказанный дефект На корпусе /root/WORK/memo-e2e (3 папки с заметками: `infra`, `work/jira`, `life/books`, всего 12 .md) команда memo index /root/WORK/memo-e2e создаёт ШЕСТЬ баз индекса вместо трёх: /root/WORK/memo-e2e/.memo/index.db /root/WORK/memo-e2e/infra/.memo/index.db /root/WORK/memo-e2e/life/.memo/index.db /root/WORK/memo-e2e/life/books/.memo/index.db /root/WORK/memo-e2e/work/.memo/index.db /root/WORK/memo-e2e/work/jira/.memo/index.db и «итого: 24 обновлено в 6 коллекциях» — одни и те же заметки проиндексированы по 2-3 раза. Причина: каталог считается коллекцией, если .md-файлы есть где-то на глубине до 2 ВНУТРИ него. ## Правило, которое надо реализовать Коллекция — это каталог, в котором есть хотя бы один файл `*.md` **непосредственно в нём самом** (не в подкаталогах). Всё остальное коллекцией не является. Правки только в memo-core/src/main/kotlin/memo/core/Collections.kt (и, при необходимости, в местах его вызова в memo-cli и memo-mcp — но менять надо только вызовы, не логику): 1. `fun findCollections(root: java.io.File): List` — рекурсивный обход root, возвращает ВСЕ каталоги (включая сам root, если подходит), в которых есть `*.md` напрямую. Скрытые каталоги (имя начинается с '.') пропускать, каталог `.memo` не обходить. Порядок — по пути, детерминированный. 2. `fun resolveCollection(path: java.io.File): java.io.File` — если path каталог, вернуть ближайший каталог, содержащий .md напрямую: сам path, иначе подняться вверх по родителям до первого такого (но не выше, чем файловая система); если не найден — вернуть сам path. Если path — файл, вернуть его родительский каталог (file.parentFile). 3. `fun isCollection(dir: java.io.File): Boolean` — вспомогательная, реализует правило из абзаца выше (ровно один уровень, без рекурсии). ## Тесты: добавить в memo-core/src/test/kotlin/memo/core/ новый файл CollectionsTest.kt Ровно 4 теста, имена ровно такие: 1. `findCollectionsReturnsOnlyDirsWithDirectMarkdown` — временный корень с такой структурой: `root/a.md`, `root/sub/b.md`, `root/sub/deep/c.md`, `root/empty/` (без .md), `root/nested/only/deep/d.md` → ровно 4 коллекции: root, root/sub, root/sub/deep, root/nested/only/deep. Каталогов `empty`, `nested`, `nested/only` в результате быть НЕ должно. 2. `parentDirWithoutDirectMarkdownIsNotCollection` — структура `root/top/inner/x.md` → findCollections даёт ровно один элемент — `root/top/inner`. 3. `resolveCollectionRaisesToNearestWithMarkdown` — для файла `root/top/inner/x.md` resolveCollection возвращает `root/top/inner`; для каталога `root/top` (внутри .md нет, есть только в inner) — тоже `root/top/inner`. 4. `hiddenDirsAreSkipped` — структура `root/.hidden/a.md`, `root/vis/b.md` → ровно одна коллекция `root/vis`. Плюс: если в корне лежит `root/.memo/index.db`, это ничего не меняет и не ломает результат. ## Обязательная сквозная проверка (сделать самому, приложить вывод) cd /root/WORK/memo ./gradlew :memo-cli:installDist -q rm -rf /root/WORK/memo-e2e/infra/.memo /root/WORK/memo-e2e/work/jira/.memo /root/WORK/memo-e2e/life/books/.memo \ /root/WORK/memo-e2e/.memo /root/WORK/memo-e2e/work/.memo /root/WORK/memo-e2e/life/.memo memo-cli/build/install/memo/bin/memo index /root/WORK/memo-e2e find /root/WORK/memo-e2e -name index.db | sort Ожидается РОВНО три пути: /root/WORK/memo-e2e/infra/.memo/index.db /root/WORK/memo-e2e/life/books/.memo/index.db /root/WORK/memo-e2e/work/jira/.memo/index.db и в выводе index — «итого: 12 обновлено в 3 коллекциях». Второй прогон index — «итого: 0 обновлено». После: ./gradlew test --rerun-tasks — ВСЕ тесты проекта зелёные (включая те 5+memo-cli+memo-mcp). Коммит: git add -A && git commit -m "core: коллекция — каталог с markdown напрямую; исправлен тройной обход" СТРОГИЕ ЗАПРЕТЫ: - Не выводить план текстом; сразу правь файлы. - Не менять логику Searcher, Indexer, Chunker, Embedder, Db. - Не менять контракт CLI (имена команд и флагов) и MCP-инструментов. - Не добавлять зависимости.