Проект: /root/WORK/memo (Kotlin/JVM). Заказ на исправление ТРЁХ доказанных дефектов демона `memo-watch`. ## Доказанные дефекты (воспроизведено на живом корпусе) Корпус `/root/WORK/memo-e2e-watch2` — копия эталонного (`infra`, `life/books`, `work/jira`, 12 .md). Все `.memo` предварительно удалены. Запуск: MEMO_MODEL_DIR=/root/WORK/memo/models/siglip2 \ memo-watch/build/install/memo-watch/bin/memo-watch /root/WORK/memo-e2e-watch2 1. **Создаёт 6 баз вместо 3.** Вывод `наблюдаю:` перечисляет корень, `infra`, `life`, `work`, `life/books`, `work/jira` и всем заводит `.memo/index.db`. Причина — своя локальная копия `discoverCollections()` в `WatchMain.kt` (walkTopDown + depth 2 «есть .md где-то внутри»); в `memo-core` эту же ошибку уже вылечили (`findCollections` = только каталог с *.md НАПРЯМУЮ), но watcher остался на старой копии. 2. **Не индексирует при старте.** Сразу после запуска (без правок файлов) в базах 0 файлов и 0 чанков; наполняется только та коллекция, в которой потом что-то изменилось. Проверка: правка одного файла в `infra` → в `infra/.memo` появились файлов 4, чанков 12, а `life/books` и `work/jira` остались с нулями. Никакого первичного прохода при старте нет. 3. **Держит все базы открытыми постоянно.** `WatchMain` открывает `Db` и `Embedder` на каждую коллекцию и не закрывает до SIGINT; тот же `.db` в это время открывают CLI и MCP. Это надо проверить на отсутствие «database is locked» (одновременные watcher + поиск). ## Что сделать Правки ТОЛЬКО в модуле `memo-watch` (`src/main` и `src/test`). `memo-core` не менять. 1. **Убрать копию логики.** В `WatchMain.kt` удалить локальную `discoverCollections` и функцию определения корня по `.memo`; использовать из ядра: `memo.core.findCollections(base)` и `memo.core.resolveCollection(raw)`. Поведение: передан один каталог-коллекция → он один; передан корень → все коллекции внутри. 2. **Первичный проход при старте.** После `watcher.start()` (или до него) выполнить для каждой коллекции `indexer.indexTree(coll)` один раз, чтобы индекс был полным сразу, без ожидания событий. Вывести в stdout по строке: `индексирую: <путь> -> обновлено `. Затем уже строки `наблюдаю:`. 3. **Устойчивость к параллельному доступу.** Включить в `Db` уже есть WAL — не менять. Вместо этого добавить в `WatchMain` обработку ошибок записи так, чтобы `database is locked` не убивал демон: при ошибке печатать в stderr и повторять попытку один раз через 1 секунду (достаточно локальной обёртки вокруг `indexer.indexTree`). Не менять код ядра. ## Тесты: дополнить memo-watch/src/test/kotlin/memo/watch/WatcherTest.kt и добавить новый файл Новый файл `memo-watch/src/test/kotlin/memo/watch/WatchMainLogicTest.kt`, ровно 2 теста: 1. `collectionsFoundByCoreRule` — дерево: `root/a.md`, `root/sub/b.md`, `root/nested/only/deep/d.md`, `root/empty/` → `findCollections(root)` даёт ровно три каталога (root, root/sub, root/nested/only/deep), и среди них НЕТ `root/nested` и `root/nested/only`. 2. `startupIndexPassFillsEveryCollection` — временный корень с двумя коллекциями (`root/c1/x.md`, `root/c2/y.md`), у каждой создаётся `Db` + `.memo`, вызывается `Indexer.indexTree(coll)` по разу (как это делает первичный проход), после чего в обеих базах `SELECT COUNT(*) FROM chunks` > 0 и `SELECT COUNT(*) FROM files` == 1. Модель брать из `MEMO_MODEL_DIR` (как в CoreSmokeTest). ## Обязательная сквозная проверка (приложить вывод) ```bash cd /root/WORK/memo ./gradlew :memo-watch:installDist -q rm -rf /tmp/mw && cp -r /root/WORK/memo-e2e /tmp/mw find /tmp/mw -name .memo -type d -prune -exec rm -rf {} + 2>/dev/null # 1) старт: 3 базы, все наполнены сразу MEMO_MODEL_DIR=/root/WORK/memo/models/siglip2 \ memo-watch/build/install/memo-watch/bin/memo-watch /tmp/mw > /tmp/mw.log 2>&1 & WPID=$! sleep 40 echo "--- базы:"; find /tmp/mw -name index.db | sort echo "--- чанки сразу после старта:" python3 - <<'EOF' import sqlite3, glob for db in sorted(glob.glob("/tmp/mw/**/.memo/index.db", recursive=True)): c = sqlite3.connect("file:"+db+"?mode=ro", uri=True) print(db.replace("/tmp/mw",""), "файлов", c.execute("SELECT COUNT(*) FROM files").fetchone()[0], "чанков", c.execute("SELECT COUNT(*) FROM chunks").fetchone()[0]) c.close() EOF # 2) параллельно с работающим демоном ищем тем же ядром — не должно быть locked memo-cli/build/install/memo/bin/memo search /tmp/mw/life/books "кто ведёт рассказ в романе" --k 3 --json | head -c 400 echo kill $WPID 2>/dev/null; wait $WPID 2>/dev/null cat /tmp/mw.log ``` Ожидается: РОВНО три пути `index.db`; у каждой коллекции ненулевые файлы и чанки СРАЗУ после старта; поиск при работающем демоне отвечает без ошибки `database is locked`; в логе — строки `индексирую: ... -> обновлено N` и `наблюдаю: ...` (по три каждого вида). После: ./gradlew test --rerun-tasks — все тесты проекта зелёные. Коммит: git add -A && git commit -m "watch: первичный проход при старте, коллекции по правилу ядра" СТРОГИЕ ЗАПРЕТЫ: - Не выводить план текстом; сразу правь файлы. - Не менять memo-core, memo-cli, memo-mcp. - Не менять смысл существующих тестов WatcherTest. - Не добавлять зависимости.