manual: инструкция «как пользоваться»; watch: первичный проход, коллекции по правилу ядра; приёмка: контур демона
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
Проект: /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).
|
||||
|
||||
## Обязательная сквозная проверка (приложить вывод)
|
||||
|
||||
```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.
|
||||
- Не добавлять зависимости.
|
||||
Reference in New Issue
Block a user