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

6.4 KiB
Raw Blame History

pr title
77 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-brainmemory, 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/pythonpython3), 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 НЕ тестировался (плагин может быть не установлен в тестовом окружении).