* 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>
5.7 KiB
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)):
-
memory-save — auto-setup + git commit + async reindex.
resolveMemoryDir()(env или~/.local/share/opencode/opencode-memory).autoSetup(): mkdir, git init (или clone еслиOPENCODE_MEMORY_REMOTEset и dir пуста), post-commit hook (auto-push, только если remote), 7 категорий.git add -A+git diff --cached --name-only→ commit с derived messagememory: 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. -
memory-search — ripgrep + Python semantic + scoring + fallback.
resolveRgBinary():@vscode/ripgrep(платформенный binary) → fallback systemrg. 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_URLset AND.rag/index.jsonсуществует. Fallback на keyword-only если Python упал/[]/env не set. Scoring — портscoreCandidateиз плагина (dist/index.js:12822-12852). Cross-category fallback если category filter дал 0 hits. -
memory-list — pure TS, без subprocess. Список категорий (count .md) или файлов в категории (title/summary/importance/updated из frontmatter).
-
memory-access — pure TS, bump frontmatter
last_accessed/access_countчерез regex replace (портbumpAccessFieldsиз плагинаdist/index.js:12677-12694). Atomic write (tmp + rename). -
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) — отклонён: добавляетmcpdep (22 transitive), Dockerfile layer, embedder.py lazy-init (сделано в PR#100), naming verification. TS tools проще для opencode-only use case (reuse provenspawnSyncpattern, 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 на systemrgчерез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.committool (для main repo) остаётся с conventional validation.