* 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>
7.4 KiB
7.4 KiB
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 Functionsandbox (с авто-добавлением.tsрасширения к specifier). Без этого loader падал на ENOENT — local imports не резолвились.- 5 GitHub tools отрефакторены на
runGh/formatResult+ добавлен опциональныйrepo?: stringпараметр (backward-compatible):create-issue.ts—runGh+formatResult(r, "gh issue create")create-pr.ts—runGh+formatResult(r, "gh pr create")post-review.ts—runGh(error-ветка оставлена кастомной с PR номером)post-docs-review.ts—runGh(аналогично)merge-pr.ts—runGh(аналогично) Приrepoomitted →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 путями + fakecloudflared(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 инфраструктуре:
- Дублирование spawnSync логики — каждый из 5 GitHub tools дублировал
spawnSync("gh", [...], { encoding, cwd })+if (r.status !== 0) return "⚠️ ... failed (exit ...): ..."— ~10 строк × 5 файлов = ~50 строк дублирования. Shared module устраняет дублирование, единый canonical error-формат. - Нет
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параметром — явный путь вместо хардкода). - 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.mjsextension —inlineShared()добавляет.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. ПриrepoomittedparseRepoвозвращает[]→--repoне добавляется → тесты проходят (51→62 на 4 tools).test_repo_omitted_no_repo_flagявно закрепляет это поведение. - Tunnel test isolation —
tunnel.shхардкодит/tmp/tunnel.pid. Тесты копируют скрипт вtmp_pathи переписывают PID/LOG пути черезreplace(). Если вtunnel.shизменится формат строкPID_FILE="..."— паттерн replace сломается, тесты упадут (раннее обнаружение). commit.tsНЕ в списке 5 — этоgit, неghtool (нет--repoфлага). Оставлен как есть..opencode/package-lock.jsonuntracked — не относится к этому PR, оставлен вне коммитов.