opencode-config/docs/decisions/027-pr-65-tools-refactor-shared-module.md
Sergey 15fc7d014d
refactor(tools): add repo parameter + shared module + tunnel tests (#65)
* refactor(tools): extract shared module for gh spawnSync logic

* feat(tools): add repo parameter to 5 GitHub tools

* test(tools): add tunnel tool tests

* test(tools): add repo parameter test cases for 5 tools

* docs(handoff): scaffold handoff and ADR for PR

* docs(handoff): set PR number

* docs(project-map): update after PR#65 structural changes

---------

Co-authored-by: opencode-agent <agent@opencode.local>
2026-07-25 19:19:19 +03:00

156 lines
No EOL
9.4 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-027: Shared module for GitHub tools + repo parameter + tunnel tests
## Статус
Accepted (2026-07-25)
## Контекст
Research (subagent explore, PR#63) выявил 3 проблемы в GitHub tools
инфраструктуре репо `slaid098/opencode-config`:
### Проблема 1: Дублирование spawnSync логики
Каждый из 5 GitHub tools (`create-issue.ts`, `create-pr.ts`, `post-review.ts`,
`post-docs-review.ts`, `merge-pr.ts`) дублировал:
- `spawnSync("gh", [...], { encoding: "utf-8", cwd: context.worktree })`
~5 строк на tool
- `if (r.status !== 0) return "⚠️ ... failed (exit ...): ..."` — ~3 строки
на tool
- Итого: ~10 строк × 5 файлов = ~50 строк дублирования
Дублирование означает: изменение canonical error-формата или spawnSync
options (например, добавление timeout) требует правки 5 файлов, дрейф
вероятен.
### Проблема 2: Нет `repo` параметра
Tools работали только через auto-detect: `gh` CLI сам определяет owner/repo
из `git remote get-url origin` в текущей директории (`context.worktree`).
Ограничения:
- Из не-git-директории (например `/root/workspace`) — `gh` не может
определить remote → fall back на hardcoded `--repo` (что было багом
issue #60, исправлено в PR#61 / ADR-025).
- Нет явного способа указать repo при вызове tool — теряется гибкость.
- Для pipeline оркестрации было бы полезно передавать repo явно (единый
стандарт вместо полагания на cwd).
ADR-025 (PR#61) убрал хардкод `--repo slaid098/opencode-config`, оставив
auto-detect. Этот PR делает следующий шаг: `repo` становится опциональным
параметром (явный путь вместо хардкода), auto-detect остаётся default.
### Проблема 3: Tunnel без тестов
Tool `tunnel` — единственный из 10 tools без парных `.py` + `.ts` тестов.
Все остальные tools покрыты (`tests/test_commit_tool.py/.ts`,
`tests/test_create_issue_tool.py/.ts`, etc.). Tunnel tool: беспараметровый,
toggle-логика в `tunnel.sh` (PID-файл `/tmp/tunnel.pid`, `kill -0` проверка
живости, stale cleanup, `CLOUDFLARE_TUNNEL_TOKEN` обязателен). Регрессии в
toggle/PID-логике проходят незамеченными.
## Решение
### 1. Shared module `.opencode/tools/_shared.ts`
Создан модуль с 3 функциями:
- `parseRepo(repo?: string): string[]` — если `repo` передан, возвращает
`["--repo", repo]`; если нет — `[]` (auto-detect через gh из cwd).
- `runGh(args: string[], repo?: string, opts?: { cwd?: string })`
`spawnSync("gh", [...parseRepo(repo), ...args], { encoding: "utf-8", cwd:
opts?.cwd })`. Prepends `--repo <name>` к gh argv когда repo явный.
- `formatResult(r, toolName: string): string` — на успехе (exit 0)
`r.stdout.trim()`, на ошибке canonical `⚠️ <toolName> failed (exit <code>):
<stderr || stdout>`.
5 GitHub tools используют `runGh` (устраняет дублирование spawnSync).
`formatResult` используют только `create-issue.ts` / `create-pr.ts` (их
error-формат точно совпадает). `post-review.ts` / `post-docs-review.ts` /
`merge-pr.ts` сохраняют кастомный error с PR номером (`⚠️ ... failed for PR
#N (exit K): ...`) для диагностики — `formatResult` потерял бы PR номер.
### 2. `repo?: string` параметр (backward-compatible)
Все 5 GitHub tools приняли опциональный `repo?: string`:
- При `repo` передан → `runGh` prepends `["--repo", repo]` → gh targetит
явный owner/name независимо от cwd.
- При `repo` omitted → `parseRepo` возвращает `[]``--repo` НЕ
добавляется → gh auto-detect'ит из `context.worktree` (cwd) — поведение
идентично pre-refactor (ADR-025 / PR#61).
Backward-compatibility подтверждена: существующие тесты `test_spawnsync_args`
(ассертят `"--repo" not in args`) + новые `test_repo_omitted_no_repo_flag`
проходят. 51→401 тестов (375 baseline + 26 новых).
### 3. `_ts_loader.mjs` extension для relative imports
Loader (`tests/_ts_loader.mjs`) использует `new Function` sandbox и умел
только `child_process` / `path` / `@opencode-ai/plugin`. Relative imports
(`./_shared`) падали с ENOENT. Расширение:
- `stripTs` стриппает `import { ... } from "./..."` + `export` на top-level
declarations + type annotations в function params (`function foo(a: Type):
Ret {``function foo(a) {`).
- `inlineShared()` инлайнит код shared-модуля в sandbox: читает оригинальный
файл для детекта import, грузит + стриппет shared-модуль, prepends к
`new Function` body. Авто-добавление `.ts` к specifier (`./_shared`
`./_shared.ts`).
Без этого тесты отрефакторенных tools падали бы — loader не поддерживал
local imports.
### 4. Tunnel tests
Созданы `tests/test_tunnel_tool.py` (6 pytest) + `tests/test_tunnel_tool.ts`
(4 TS). `.py` тесты запускают `tunnel.sh` напрямую с изоляцией (копия в
`tmp_path`, переписанные PID/LOG пути, fake `cloudflared` = `sleep 30` на
PATH). Покрыты все ветви: start без токена (exit 1), start с токеном, start
с `TUNNEL_DOMAIN`, stop при живом процессе, stale PID cleanup + start,
toggle (повторный start → stop).
### 5. 5 test-файлов обновлены для `repo` параметра
Каждый из 5 tools получил 3 новых кейса: `test_repo_explicit_passed_to_gh`
(`--repo foo/bar` prepended), `test_repo_omitted_no_repo_flag` (no `--repo`,
backward-compat), `test_repo_invalid_gh_error` (invalid repo + gh failure →
tool-specific error). `test_merge_pr_tool.py/.ts` созданы с нуля (merge-pr
не имел тестов ранее) — 8 .py + 5 .ts покрывают базовые кейсы + repo.
## Альтернативы
- **Оставить дублирование как есть** — отклонено: ~50 строк дублирования
растёт с каждым новым gh tool, дрейф в error-формате/spawnSync options
вероятен. Shared module — canonical источник, изменение в 1 месте.
- **Mandatory `repo` параметр (убрать auto-detect)** — отклонено: теряет
backward-compatibility. Существующие вызовы tools (из pipeline, из
агентов) не передают `repo` — полагаются на auto-detect из
`context.worktree`. Mandatory `repo` сломал бы все вызовы, потребовал бы
обновления skills/agents. Опциональный `repo?: string` сохраняет
auto-detect default + даёт явный путь когда нужно.
- **`formatResult` для всех 5 tools (убрать кастомный error с PR номером)** —
отклонено: post-review/post-docs-review/merge-pr error-сообщения
содержат `for PR #N` — полезно для диагностики (какой PR fail'нул).
`formatResult` дал бы generic `⚠️ post-review failed (exit K): ...` без
PR. Потеря информации > экономия 3 строк × 3 файла. `formatResult`
используется где error-формат точно совпадает (create-issue/create-pr).
- **Параметризовать `--repo` через `git remote get-url origin` (как
ADR-007 для pipeline-status.py)** — отклонено: достаточно опционального
`repo` + auto-detect. Параметризация через git remote добавила бы spawn
вызов в каждый tool без выгоды (auto-detect из cwd справляется, а явный
`repo` покрывает edge-cases). ADR-025 rejected alt пересмотрена.
- **Inline shared-логику в каждый tool без отдельного модуля** — отклонено:
это и есть текущее дублирование (проблема 1). Вынос в `_shared.ts`
canonical источник, тестируемый отдельно.
- **Не расширять `_ts_loader.mjs`, тестировать только через `.ts` (bun)** —
отклонено: CI runner не имеет `bun` (opencode binary с bundled bun, нет
отдельного CLI). `.ts` тесты документационные; реальные проверки идут
через `.py` + loader. Без расширения loader'а `.py` тесты отрефакторенных
tools падали бы.