Files
memo/docs/orders/11-watch-fix.md
T

7.1 KiB
Raw Blame History

Проект: /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 по строке: индексирую: <путь> -> обновлено <N>. Затем уже строки наблюдаю:.
  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).

Обязательная сквозная проверка (приложить вывод)

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.
  • Не добавлять зависимости.