feat(merge-pr): delete local PR branch after merge + wrong-repo guard #30

Merged
slaid098 merged 5 commits from feat/merge-pr/local-branch-cleanup into main 2026-08-08 14:07:50 +03:00
Owner

Что сделано

  • .opencode/tools/_shared.ts: экспортирован resolveForgejoRepo, добавлены хелперы getPull (GET /repos/{full}/pulls/{n}) и getRepo (GET /repos/{full}) для pre-flight.
  • .opencode/tools/merge-pr.ts: pre-flight перед POST merge — проверка существования PR (404 → wrong-repo диагностика «ты не в том проекте», мерж НЕ выполняется) и получение default_branch. После мержа — локальная очистка: удаление только ветки смерженного PR (head.ref); если текущая ветка == head.ref → checkout default + pull + branch -D; если != → только branch -D; если ветки нет → пропуск. Если репо PR != worktree → мерж без локальной очистки с явным сообщением. Если pull упал → «мерж прошёл, но pull не выполнен», ветка всё равно удаляется. Обновлён description тула.
  • .opencode/skills/run-pipeline/SKILL.md Template F: при ошибке «PR not found / 404» → STOP + проверка проекта (worktree ≠ репо PR), НЕ retry с repo аргументом.
  • tests/test_merge_pr_tool.py + tests/_ts_loader.mjs: новые стабы (git branch --list/-D, checkout, pull, rev-parse) и 7 новых тестов (404 pre-flight, cleanup с checkout+pull, без переключения, repo mismatch, отсутствие ветки, pull failure). Старые тесты адаптированы под pre-flight.

Почему

После мержа PR через merge_pr tool локальная ветка PR оставалась в worktree (Forgejo удаляет только remote-ветку), засоряя git branch и ломая следующий git checkout main && git pull. Вторая проблема — wrong-repo: из чужого worktree POST merge уходил в неверный репо → 404 без объяснения.

Watch out

  • Локальные git-операции выполняются только через spawnSync внутри тула (plugin tools не проходят через permission.bash).
  • Default branch берётся из GET /repos/{full} (default_branch), не хардкодится.
  • Удаляется ТОЛЬКО ветка смерженного PR — чужие/старые ветки не трогаются.
  • resolveForgejoRepo теперь вызывается до 3 раз (pre-flight, merge, сравнение worktree) — тесты учитывают это.

Pending

—

Closes #28

## Что сделано - `.opencode/tools/_shared.ts`: экспортирован `resolveForgejoRepo`, добавлены хелперы `getPull` (GET /repos/{full}/pulls/{n}) и `getRepo` (GET /repos/{full}) для pre-flight. - `.opencode/tools/merge-pr.ts`: pre-flight перед POST merge — проверка существования PR (404 → wrong-repo диагностика «ты не в том проекте», мерж НЕ выполняется) и получение `default_branch`. После мержа — локальная очистка: удаление только ветки смерженного PR (head.ref); если текущая ветка == head.ref → checkout default + pull + branch -D; если != → только branch -D; если ветки нет → пропуск. Если репо PR != worktree → мерж без локальной очистки с явным сообщением. Если pull упал → «мерж прошёл, но pull не выполнен», ветка всё равно удаляется. Обновлён description тула. - `.opencode/skills/run-pipeline/SKILL.md` Template F: при ошибке «PR not found / 404» → STOP + проверка проекта (worktree ≠ репо PR), НЕ retry с repo аргументом. - `tests/test_merge_pr_tool.py` + `tests/_ts_loader.mjs`: новые стабы (git branch --list/-D, checkout, pull, rev-parse) и 7 новых тестов (404 pre-flight, cleanup с checkout+pull, без переключения, repo mismatch, отсутствие ветки, pull failure). Старые тесты адаптированы под pre-flight. ## Почему После мержа PR через merge_pr tool локальная ветка PR оставалась в worktree (Forgejo удаляет только remote-ветку), засоряя `git branch` и ломая следующий `git checkout main && git pull`. Вторая проблема — wrong-repo: из чужого worktree POST merge уходил в неверный репо → 404 без объяснения. ## Watch out - Локальные git-операции выполняются только через spawnSync внутри тула (plugin tools не проходят через permission.bash). - Default branch берётся из GET /repos/{full} (`default_branch`), не хардкодится. - Удаляется ТОЛЬКО ветка смерженного PR — чужие/старые ветки не трогаются. - `resolveForgejoRepo` теперь вызывается до 3 раз (pre-flight, merge, сравнение worktree) — тесты учитывают это. ## Pending — Closes #28
test(merge-pr): cover pre-flight and local cleanup paths
Some checks failed
CI (always) / bootstrap (pull_request) Successful in 2s
CI / bootstrap (pull_request) Successful in 5s
CI / complexity (pull_request) Successful in 28s
CI / typecheck (pull_request) Successful in 29s
CI / lint (pull_request) Failing after 29s
CI / test (3.13) (pull_request) Successful in 1m32s
81bd44d0d7
fix(tests): wrap long lines and fix undefined name in merge-pr tests
All checks were successful
CI (always) / bootstrap (pull_request) Successful in 9s
CI / bootstrap (pull_request) Successful in 12s
CI / lint (pull_request) Successful in 28s
CI / typecheck (pull_request) Successful in 28s
CI / complexity (pull_request) Successful in 28s
CI / test (3.13) (pull_request) Successful in 1m28s
8bef240017
Author
Owner

Code Review Summary

Качественный PR: pre-flight wrong-repo guard (404 → диагностика «не в том проекте», merge НЕ выполняется) + локальная очистка ветки смерженного PR (checkout default + pull + branch -D, только при совпадении worktree и репо PR). 7 новых тестов покрывают все ветки cleanup, стабы в _ts_loader.mjs аккуратно изолированы через env-переменные. Cross-file связи проверены: pipeline-status.py:check_merge читает state PR (не сообщение тула) — не сломан; run-pipeline/SKILL.md Template F обновлён парно в этом же PR; reviewer/memory-syncer не зависят от merge-pr сообщений.

Positives

  • Wrong-repo guard: 404 на pre-flight GET /pulls/{n} → понятная диагностика, merge POST не вызывается (тест test_preflight_404_wrong_repo_diagnostic это проверяет).
  • Cleanup безопасен: удаляется ТОЛЬКО ветка смерженного PR (branch --list + rev-parse проверки), repo mismatch → пропуск с явным сообщением.
  • Graceful degradation: pull failure → «мерж прошёл, но pull не выполнен», ветка всё равно удаляется; checkout failure → пропуск очистки без фейла.
  • spawnSync без shell — head.ref из API не может вызвать shell-инъекцию.
  • Тесты: 7 новых + адаптация старых под pre-flight (3 fetch calls, 3 remote lookups), стабы через env (GIT_HEAD_STUB, GIT_PULL_STUB и т.д.) — детерминированно.
  • PR body полный (Что сделано / Почему / Watch out), Closes #28.

Warnings (should fix, не блокируют)

  • tests/test_merge_pr_tool.ts:46,81 [consistency] TS-зеркало не обновлено: ожидает старый формат "PR #42 merged successfully (squash, branch deleted).", а тул теперь возвращает "PR #42 merged (squash, remote branch deleted). Local ... deleted, switched to ...". Файл не запускается в CI (нет bun, только документация), но при будущем bun test упадёт. Обнови ожидания или удали файл.
    Fix: привести 4 теста к новому формату сообщений (как в test_merge_pr_tool.py).
  • .opencode/tools/merge-pr.ts:77-85 [correctness] В ветке currentBranch === headRef при pull failure сообщение утверждает Local ${headRef} deleted, даже если git branch -D тоже упал (del выполняется до проверки pull, его статус теряется). Двойной failure — редкий edge case, но сообщение вводит в заблуждение.
    Fix: проверять del.status перед утверждением об удалении, например: if (pull.status !== 0) return ... Мерж прошёл, но pull не выполнен: ... ${del.status === 0 ? \Local ${headRef} deleted` : `Local ${headRef} НЕ удалена: ${del.stderr}`}. Switched to ${defaultBranch}.`

Suggestions (info, not blocking)

  • .opencode/tools/merge-pr.ts:16,48 [perf] resolveForgejoRepo вызывается до 3 раз (pre-flight, runGh dispatch, worktree сравнение) — 3 spawnSync git. Можно закешировать результат в переменную.
  • tests/_ts_loader.mjs:365-380 [tests] Стабы GIT_CHECKOUT_STUB и GIT_BRANCH_D_STUB добавлены, но тестов на checkout failure и delete failure нет — стоит добавить (покрытие веток строк 71-76 и 86-91).
  • .opencode/tools/merge-pr.ts:60 [edge] git branch --list <headRef> интерпретирует headRef как glob-pattern: если head.ref PR содержит */?, --list вернёт несколько веток, и branch -D удалит все матчащиеся. head.ref контролируется автором PR (внутренний инструмент, риск минимален), но можно использовать git branch --list --format='%(refname:short)' <headRef> + точное сравнение.

Verdict: APPROVE

## Code Review Summary Качественный PR: pre-flight wrong-repo guard (404 → диагностика «не в том проекте», merge НЕ выполняется) + локальная очистка ветки смерженного PR (checkout default + pull + branch -D, только при совпадении worktree и репо PR). 7 новых тестов покрывают все ветки cleanup, стабы в _ts_loader.mjs аккуратно изолированы через env-переменные. Cross-file связи проверены: pipeline-status.py:check_merge читает state PR (не сообщение тула) — не сломан; run-pipeline/SKILL.md Template F обновлён парно в этом же PR; reviewer/memory-syncer не зависят от merge-pr сообщений. ### Positives - Wrong-repo guard: 404 на pre-flight GET /pulls/{n} → понятная диагностика, merge POST не вызывается (тест test_preflight_404_wrong_repo_diagnostic это проверяет). - Cleanup безопасен: удаляется ТОЛЬКО ветка смерженного PR (branch --list + rev-parse проверки), repo mismatch → пропуск с явным сообщением. - Graceful degradation: pull failure → «мерж прошёл, но pull не выполнен», ветка всё равно удаляется; checkout failure → пропуск очистки без фейла. - spawnSync без shell — head.ref из API не может вызвать shell-инъекцию. - Тесты: 7 новых + адаптация старых под pre-flight (3 fetch calls, 3 remote lookups), стабы через env (GIT_HEAD_STUB, GIT_PULL_STUB и т.д.) — детерминированно. - PR body полный (Что сделано / Почему / Watch out), Closes #28. ### Warnings (should fix, не блокируют) - **tests/test_merge_pr_tool.ts:46,81** [consistency] TS-зеркало не обновлено: ожидает старый формат `"PR #42 merged successfully (squash, branch deleted)."`, а тул теперь возвращает `"PR #42 merged (squash, remote branch deleted). Local ... deleted, switched to ..."`. Файл не запускается в CI (нет bun, только документация), но при будущем `bun test` упадёт. Обнови ожидания или удали файл. Fix: привести 4 теста к новому формату сообщений (как в test_merge_pr_tool.py). - **.opencode/tools/merge-pr.ts:77-85** [correctness] В ветке `currentBranch === headRef` при pull failure сообщение утверждает `Local ${headRef} deleted`, даже если `git branch -D` тоже упал (del выполняется до проверки pull, его статус теряется). Двойной failure — редкий edge case, но сообщение вводит в заблуждение. Fix: проверять `del.status` перед утверждением об удалении, например: `if (pull.status !== 0) return ... Мерж прошёл, но pull не выполнен: ... ${del.status === 0 ? \`Local ${headRef} deleted\` : \`Local ${headRef} НЕ удалена: ${del.stderr}\`}. Switched to ${defaultBranch}.` ### Suggestions (info, not blocking) - **.opencode/tools/merge-pr.ts:16,48** [perf] `resolveForgejoRepo` вызывается до 3 раз (pre-flight, runGh dispatch, worktree сравнение) — 3 spawnSync git. Можно закешировать результат в переменную. - **tests/_ts_loader.mjs:365-380** [tests] Стабы GIT_CHECKOUT_STUB и GIT_BRANCH_D_STUB добавлены, но тестов на checkout failure и delete failure нет — стоит добавить (покрытие веток строк 71-76 и 86-91). - **.opencode/tools/merge-pr.ts:60** [edge] `git branch --list <headRef>` интерпретирует headRef как glob-pattern: если head.ref PR содержит `*`/`?`, `--list` вернёт несколько веток, и `branch -D` удалит все матчащиеся. head.ref контролируется автором PR (внутренний инструмент, риск минимален), но можно использовать `git branch --list --format='%(refname:short)' <headRef>` + точное сравнение. ### Verdict: APPROVE
slaid098 deleted branch feat/merge-pr/local-branch-cleanup 2026-08-08 14:07:51 +03:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
slaid098/opencode-config!30
No description provided.