opencode-config/docs/decisions/043-pr-101-ts-memory-tools.md
Sergey 45bbdc50f3
feat(memory): 5 TS tools with auto-setup and fallback (#101)
* feat(memory): memory-save tool with auto-setup

* feat(memory): memory-search with keyword and semantic fallback

* feat(memory): memory-list and memory-access tools

* feat(memory): memory-doctor diagnostic tool

* docs(handoff): add handoff and ADR for ts memory tools

* docs(handoff): set PR number

* docs(handoff): finalize pr-101 frontmatter and pending

* docs(project-map): add 5 memory TS tools after PR#101

* refactor(memory): extract shared helpers to _memory-shared.ts

* refactor(memory): split oversized execute functions

* fix(memory): handle git checkout and catch blocks

---------

Co-authored-by: opencode-agent <agent@opencode.local>
2026-07-27 01:12:51 +03:00

5.7 KiB
Raw Blame History

ADR-043: 5 TS memory tools with auto-setup and progressive enhancement

Статус

Accepted (2026-07-26)

Контекст

Плагин @mathew-cf/opencode-memory (MCP server wrapper) не вызывается из-за бага wrapper-пути: MEMORY_WRAPPER_PATH в setup-memory.sh указывает на @mathew-cf/rag-cli/bin/rag.js, который не существует или несовместим (GLIBC 2.39 vs 2.36 — см. ADR-030, PR#57, PR#71). Плагин деградирует до keyword-only через grep, но wrapper-hop добавляет fragility. Нужны нативные TS tools, вызывающие Python через spawnSync (паттерн pipeline-status.ts, commit.ts, memory-setup.ts).

Фаза 1 (PR#100) сделала embedder lazy-init (returns None без OPENAI_BASE_URL, не raises), index.py логирует reindex в .rag/reindex.log, search.py обрабатывает None от embedder → возвращает []. Это разблокировало TS tools: Python backend безопасен при отсутствии env vars.

Требования к миграции (issue #96, #94):

  • 5 tools: memory-save, memory-search, memory-list, memory-access, memory-doctor
  • Keyword search (ripgrep) работает ВСЕГДА (zero env dependency)
  • Semantic search (Python + OpenRouter) как enhancement с fallback на keyword-only
  • Zero-config: первый memory-save авто-инициализирует память (mkdir + git init/clone + hook + categories)
  • Scoring портирован из плагина scoreCandidate (rgMatch + ragScore×1.4 + tags + importance + accessCount)
  • Pure-orchestrator model (ADR-010): TS tools вызываются из главного чата, делегируют в Python через subprocess

Решение

5 TS tools в .opencode/tools/, каждый следует паттерну tool() из @opencode-ai/plugin (description + args (zod) + async execute(args, context)):

  1. memory-save — auto-setup + git commit + async reindex. resolveMemoryDir() (env или ~/.local/share/opencode/opencode-memory). autoSetup(): mkdir, git init (или clone если OPENCODE_MEMORY_REMOTE set и dir пуста), post-commit hook (auto-push, только если remote), 7 категорий. git add -A + git diff --cached --name-only → commit с derived message memory: sync <files>. Async reindex: spawn(python3, ["-m","src.memory","index",...], {detached:true, stdio:"ignore"}) + child.unref() (fire-and-forget). Skip semantic если OPENAI_BASE_URL не set.

  2. memory-search — ripgrep + Python semantic + scoring + fallback. resolveRgBinary(): @vscode/ripgrep (платформенный binary) → fallback system rg. Keyword: rg -il --glob *.md --glob !.git --glob !.rag --glob !**/INDEX.md -e <term> per term (OR). Semantic: spawnSync("python3", ["-m","src.memory","search",...]) если OPENAI_BASE_URL set AND .rag/index.json существует. Fallback на keyword-only если Python упал/[]/env не set. Scoring — порт scoreCandidate из плагина (dist/index.js:12822-12852). Cross-category fallback если category filter дал 0 hits.

  3. memory-list — pure TS, без subprocess. Список категорий (count .md) или файлов в категории (title/summary/importance/updated из frontmatter).

  4. memory-access — pure TS, bump frontmatter last_accessed/access_count через regex replace (порт bumpAccessFields из плагина dist/index.js:12677-12694). Atomic write (tmp + rename).

  5. memory-doctor — read-only диагностика (rg, Python importability, env vars, memory dir, RAG index).

Зависимости: @vscode/ripgrep ^1.18.0 (keyword search, платформенный binary), devDeps @types/node + typescript (typecheck).

Альтернативы

  • Python MCP server (Option A в technical/cloud-memory-opencode-landscape.md, technical/python-mcp-server-replace-plugin-feasibility.md) — отклонён: добавляет mcp dep (22 transitive), Dockerfile layer, embedder.py lazy-init (сделано в PR#100), naming verification. TS tools проще для opencode-only use case (reuse proven spawnSync pattern, no new Python deps, no Dockerfile changes).
  • Оставить плагин + починить wrapper — отклонено: wrapper-hop (setup-memory.sh → JS wrapper → Python CLI) добавляет fragility, @mathew-cf/rag-cli требует GLIBC 2.39 (несовместим с node:20-slim на Bookworm, см. ADR-030). TS tools вызывают Python напрямую через python3 -m src.memory.
  • System rg без @vscode/ripgrep — отклонён: системный rg может отсутствовать (Docker slim images). @vscode/ripgrep предоставляет платформенный binary (npm-managed, reproducible). memory-search имеет fallback на system rg через spawnSync("rg", ["--version"]).
  • Полноценный YAML parser (js-yaml) для frontmatter — отклонён: наш формат плоский (плоские ключи + [a, b] для tags/related). Regex-парсер (тот же что в плагине parseFrontmatter) покрывает формат и не добавляет dep.
  • Conventional commit format для memory-save commits — отклонён: memory repo не part основного репо, commit message derived из changed files (memory: sync <files>), не human-curated. Тот же паттерн что в плагине runSave. commit tool (для main repo) остаётся с conventional validation.