opencode-config/docs/handoff/pr-77-wire-plugin-via-setup-memory.md
Sergey 7c1fa4985e
feat(memory): wire CLI into plugin via setup-memory.sh wrapper (#77)
* feat(memory): wire setup-memory.sh to Python CLI + JS wrapper

* test(memory): add wrapper generation, idempotency, backup tests

* docs(memory): update project map for setup-memory wrapper integration

* docs(handoff): add handoff and ADR-033 for plugin wiring

* docs(handoff): set PR number

* docs(handoff): fix Pending em-dash typo in PR#77 handoff

---------

Co-authored-by: opencode-agent <agent@opencode.local>
2026-07-26 17:06:45 +03:00

30 lines
No EOL
6.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
pr: 77
title: Wire memory CLI into opencode-memory plugin via setup-memory.sh wrapper
---
## Что сделано
- `.opencode/scripts/setup-memory.sh`: добавлена `WRAPPER_PATH` env-переменная (default `/usr/local/lib/node_modules/@mathew-cf/opencode-memory/node_modules/@mathew-cf/rag-cli/bin/rag.js`, override через `MEMORY_WRAPPER_PATH`).
- `.opencode/scripts/setup-memory.sh`: добавлен выбор Python-интерпретатора `MEMORY_PYTHON` — prefer `${OPENCODE_WORKSPACE}/.venv/bin/python`, then `.venv/bin/python`, then `python3` (override через `MEMORY_WRAPPER_PYTHON`). Причина: system `python3` не имеет `httpx` (нет venv), CLI падает на import — wrapper должен вызывать venv-python.
- `.opencode/scripts/setup-memory.sh` шаг 5: `command -v rag``"$MEMORY_PYTHON" -c "import src.memory"`. Если доступен → `python -m src.memory index "$MEMORY_DIR" -o "$MEMORY_DIR/.rag"`. Если нет → echo skip + continue (best-effort, не exit 1).
- `.opencode/scripts/setup-memory.sh` шаг 5b (новый): генерация JS wrapper для плагина `@mathew-cf/opencode-memory`. Wrapper содержит `spawnSync(py, ["-m", "src.memory", ...process.argv.slice(2)], { cwd: OPENCODE_WORKSPACE })` — делегирует `rag index/search/download` в Python memory CLI. Idempotent через `cmp -s` (сравнение контента, НЕ `$(cat)` — bash `$()` удаляет trailing newline, давал false-negative). Backup `.orig` оригинала один раз (не перезаписывается). `chmod +x`.
- `tests/test_setup_memory.py`: `_run_script` расширен `wrapper: Path | None` параметром → `MEMORY_WRAPPER_PATH` env override. Все существующие тесты обновлены передавать `wrapper=tmp_path/"wrapper"/"rag.js"` (герметичность — не пишут в `/usr/local/lib/...`). insteadOf-тесты (прямые `subprocess.run`) тоже обновлены. mypy type-args починены (`CompletedProcess[str]`, `dict[str, str | None]`).
- `tests/test_setup_memory.py`: +4 новых теста — `test_wrapper_generated` (wrapper существует + executable), `test_wrapper_idempotent` (2 запуска → контент идентичен + "wrapper correct"), `test_wrapper_content` (содержит `python`, `-m`, `src.memory`, `spawnSync`, `process.argv.slice`), `test_wrapper_backup_original` (`.orig` сохраняет старый контент, НЕ перезаписывается при 2-й замене).
- `docs/project-map/README.md`: setup-memory.sh entry обновлён (6-step → 7-step, +JS wrapper), test_setup_memory.py entry обновлён (+wrapper gen/backup).
- ADR-033 + этот handoff.
## Почему
PR #75 (merged) отрефакторил Memory CLI (`second-brain``memory`, OpenAI env, chunking, batching, dedup), но плагин `@mathew-cf/opencode-memory` всё ещё вызывал родной Rust `@mathew-cf/rag-cli` — 5-10 мин на 1.2 MB на CPU, 3 зомби-процесса зафиксировано, индекс никогда не записан (`$OPENCODE_MEMORY_DIR/.rag` отсутствовал). Плагин находит rag через `require.resolve("@mathew-cf/rag-cli/bin/rag.js")` (вендорный `dist/index.js:12703-12708`, НЕ патчим).
Решение: setup-memory.sh генерирует JS wrapper по пути, который резолвит плагин. Wrapper = прагматичный shim (аналог pyenv/nvm shims), перехватывает `require.resolve` → перенаправляет вызовы в Python `memory` CLI. Идемпотентен (content check через `cmp -s`), backup `.orig` оригинала один раз. PR #2 в серии из 2 (после PR #75).
Отклонение от спеки: спека говорила `spawnSync("python3", ["-m", "src.memory", ...])` — но system `python3` не имеет `httpx` (нет venv), CLI падает на import. Реализация выбирает venv-python (`${OPENCODE_WORKSPACE}/.venv/bin/python``.venv/bin/python``python3`), wrapper встраивает выбранный путь. Зафиксировано в ADR-033 Альтернативы.
## Pending
## Watch out
- **`$(cat)` vs `cmp -s` для idempotency.** Bash `$()` удаляет trailing newlines — `$(cat file)` возвращает контент без `\n` в конце, а `$VAR` (одиночные кавычки) содержит `\n` перед закрывающей `'`. Сравнение `[ "$(cat)" = "$VAR" ]` = false даже при идентичном контенте. Решение: `cmp -s "$WRAPPER_PATH" /dev/stdin <<<"$WRAPPER_CONTENT"` — байт-точное сравнение. НЕ возвращаться к `$(cat)`.
- **Wrapper встраивает абсолютный путь к Python.** `MEMORY_PYTHON` разрешается на момент запуска setup-memory.sh и встраивается в wrapper как literal string. Если venv переедет — wrapper нужно перегенерировать (удалить wrapper, запустить setup-memory.sh). Альтернатива (читать env в runtime через `process.env.MEMORY_WRAPPER_PYTHON`) — оставлена как override, но default встраивается для надёжности (env может быть не set в контексте плагина).
- **`MEMORY_WRAPPER_PATH` env override обязателен для тестов.** Без него скрипт пишет в `/usr/local/lib/node_modules/@mathew-cf/opencode-memory/...` — real plugin path. Все тесты передают `wrapper=tmp_path/"wrapper"/"rag.js"`. Smoke-test может использовать real path (если плагин установлен).
- **Smoke-test пройден (2026-07-26):** `bash .opencode/scripts/setup-memory.sh` с `MEMORY_WRAPPER_PATH=/tmp/...` → wrapper сгенерирован, `.rag/index.json` создан через облако (1 файл, ~42KB), idempotency подтверждена (2-й запуск → "wrapper correct"), backup `.orig` работает. Реальный plugin path НЕ тестировался (плагин может быть не установлен в тестовом окружении).