opencode-config/docs/handoff/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

7.4 KiB
Raw Permalink Blame History


pr: 65 title: refactor(tools): add repo parameter + shared module + tunnel tests

Что сделано

  • .opencode/tools/_shared.ts — новый shared module: parseRepo(repo?) (возвращает ["--repo", repo] или [] для auto-detect), runGh(args, repo?, opts?) (spawnSync с prepended --repo при явном repo + cwd из opts), formatResult(r, toolName) (canonical ⚠️ <tool> failed (exit K): ... на ошибке, stdout.trim() на успехе). Устраняет ~50 строк дублирования spawnSync-логики в 5 GitHub tools.
  • tests/_ts_loader.mjs — расширение для поддержки relative imports (./_shared): stripTs стриппает import { ... } from "./..." + export на top-level declarations + type annotations в function params; новая inlineShared() инлайнит код shared-модуля в new Function sandbox (с авто-добавлением .ts расширения к specifier). Без этого loader падал на ENOENT — local imports не резолвились.
  • 5 GitHub tools отрефакторены на runGh/formatResult + добавлен опциональный repo?: string параметр (backward-compatible):
    • create-issue.tsrunGh + formatResult(r, "gh issue create")
    • create-pr.tsrunGh + formatResult(r, "gh pr create")
    • post-review.tsrunGh (error-ветка оставлена кастомной с PR номером)
    • post-docs-review.tsrunGh (аналогично)
    • merge-pr.tsrunGh (аналогично) При repo omitted → parseRepo возвращает [], gh auto-detect'ит из context.worktree (cwd) — поведение идентично pre-refactor (ADR-025 / PR#61).
  • Tunnel tests созданы (issue #64 проблема 3 — единственный tool без тестов):
    • tests/test_tunnel_tool.py — 6 pytest-тестов: start без токена (exit 1), start с токеном (started (PID: N)), start с TUNNEL_DOMAIN (domain: ...), stop при живом процессе (stopped + PID-файл удалён), stale PID-файл (чистка + start), toggle (повторный start → stop). Изоляция: копия tunnel.sh в tmp с переписанными PID/LOG путями + fake cloudflared (sleep 30s) на PATH.
    • tests/test_tunnel_tool.ts — 4 TS-теста: spawnSync с bash + путём к скрипту, success → trimmed stdout, failure → ⚠️ tunnel failed (exit K), cwd из context.
  • 5 test-файлов обновлены кейсами для repo?: string (явный / auto-detect / invalid):
    • test_create_issue_tool.py/.ts — +3 кейса (explicit, omitted, invalid)
    • test_create_pr_tool.py/.ts — +3 кейса
    • test_post_review_tool.py/.ts — +3 кейса
    • test_post_docs_review_tool.py/.ts — +3 кейса
    • test_merge_pr_tool.py/.ts — созданы с нуля (8 .py + 5 .ts: базовые кейсы + repo; merge-pr не имел тестов ранее)
  • ADR-027 + этот handoff.

Почему

Research (subagent explore, PR#63) выявил 3 проблемы в GitHub tools инфраструктуре:

  1. Дублирование spawnSync логики — каждый из 5 GitHub tools дублировал spawnSync("gh", [...], { encoding, cwd }) + if (r.status !== 0) return "⚠️ ... failed (exit ...): ..." — ~10 строк × 5 файлов = ~50 строк дублирования. Shared module устраняет дублирование, единый canonical error-формат.
  2. Нет repo параметра — tools работали только через auto-detect (gh определяет owner/repo из git remote в context.worktree). Ограничение: из не-git-директории gh не может определить remote → fall back на хардкод (баг issue #60, исправлен в PR#61). Явный repo?: string даёт гибкость для pipeline оркестрации без потери auto-detect по умолчанию (backward-compatible). Связано с ADR-025 (PR#61 убрал хардкод, этот PR делает repo параметром — явный путь вместо хардкода).
  3. Tunnel без тестовtunnel единственный из 10 tools без парных .py + .ts тестов. Toggle-логика (PID-файл, kill -0, stale cleanup) не покрыта → регрессии проходят незамеченными. 6 .py + 4 .ts тестов покрывают все ветви tunnel.sh.

Pending

  • После merge: на хосте git pull + рестарт opencode-контейнера чтобы подхватились обновлённые tools (auto-discovered через @opencode-ai/plugin).
  • Зависимости: supersedes partial PR#61 (ADR-025) — убрал хардкод --repo, этот PR параметризует; related PR#63 (ADR-026) — tool-usage policy описывает 10 tools, этот PR улучшает их реализацию.

Watch out

  • _ts_loader.mjs extensioninlineShared() добавляет .ts к relative import specifier если его нет (./_shared./_shared.ts). Это необходимо потому что TS import specifiers опускают расширение, а readFileSync требует полный путь. Если будущие shared-модули используют другие расширения (.tsx, .mts) — потребуется расширить логику.
  • formatResult только для create-issue/create-pr — post-review, post-docs-review, merge-pr сохраняют кастомный error с PR номером (⚠️ ... failed for PR #N (exit K): ...) для диагностики. formatResult дал бы ⚠️ ... failed (exit K): ... без PR — потеря информации. Тесты test_gh_failure_returns_error проверяют substring "post-review failed" / "exit 1"оба варианта проходят.
  • Backward-compat критична — существующие test_spawnsync_args в post-review/post-docs-review ассертят "--repo" not in args. При repo omitted parseRepo возвращает []--repo не добавляется → тесты проходят (51→62 на 4 tools). test_repo_omitted_no_repo_flag явно закрепляет это поведение.
  • Tunnel test isolationtunnel.sh хардкодит /tmp/tunnel.pid. Тесты копируют скрипт в tmp_path и переписывают PID/LOG пути через replace(). Если в tunnel.sh изменится формат строк PID_FILE="..." — паттерн replace сломается, тесты упадут (раннее обнаружение).
  • commit.ts НЕ в списке 5 — это git, не gh tool (нет --repo флага). Оставлен как есть.
  • .opencode/package-lock.json untracked — не относится к этому PR, оставлен вне коммитов.