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

39 lines
No EOL
5.7 KiB
Markdown
Raw Permalink 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.

# 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.