fix(tools): cleanup local pr-N branches in merge-pr tool #53

Merged
slaid098 merged 4 commits from fix/tools/merge-pr-cleanup-pr-branches into main 2026-08-11 17:54:39 +03:00
Owner

Что сделано

  • .opencode/tools/merge-pr.ts: добавлен helper cleanupPrBranch(prNumber, cwd) —
    guard через git branch --list pr-N (пустой stdout → skip), git branch -D pr-N
    при наличии, soft fail ⚠️ failed to delete local pr-N branch: <stderr> при ошибке
    (НЕ блокирует merge). Вызов добавлен во все return-точки после успешного merge
    (кроме repo-mismatch path — там локальная очистка пропущена целиком).
  • tests/_ts_loader.mjs: stub git branch --list расширен — для target pr-*
    по умолчанию возвращает пустой stdout (существующие тесты не ломаются), override
    через GIT_PR_BRANCH_LIST_STUB (например "pr-45") для тестов pr-N cleanup.
  • tests/test_merge_pr_tool.py: 2 новых теста
    (test_merge_pr_cleans_pr_branch, test_merge_pr_no_pr_branch) — покрывают
    presence/absence pr-N ветки. Существующие 15 тестов без регрессии.
  • docs/decisions/098-pr-53-merge-pr-cleanup-pr-branches.md: ADR с обоснованием
    fix в merge-pr.ts (deterministic, single source of truth) vs run-pipeline/SKILL.md
    Template D manual cleanup (протокольное изменение, race condition, не покрывает
    PR без fix_ci).

Почему

run-pipeline/SKILL.md Template D (fix_ci) создаёт локальные ветки pr-M через
git fetch origin pull/M/head:pr-M, но merge-pr.ts чистил только оригинальную
feature-ветку (по headRef), а pr-M не трогал — ветки копились бесконечно.
В репо уже накопилось 9 мусорных веток (pr-19, pr-25, pr-280, ...).
Cleanup в merge-pr.ts deterministic: tool вызывается ровно один раз на PR
(merge phase), не зависит от того, запускал ли агент fix_ci.

Watch out

  • Cleanup ТОЛЬКО pr-${pr_number} (текущего PR) — НЕ все pr-* ветки
    (иначе можно удалить pr-N другого PR при parallel work).
  • pr-39-review (не-стандартное имя с суффиксом) НЕ матчится pr-39 —
    existing pr-39-review ручная мусорная ветка, не удаляется автоматически.
  • Repo-mismatch path (worktreeRepo !== full) → cleanup пропущен целиком
    (headRef и pr-N) — worktree не принадлежит PR repo.
  • GIT_PR_BRANCH_LIST_STUB по умолчанию пустой — это намеренно, чтобы
    существующие тесты (не знающие про pr-N cleanup) не видели pr-N ветку
    и не получали diff в expected messages. Тесты с pr-N cleanup явно
    выставляют stub.

Pending

  • Cleanup существующих 9 мусорных веток (pr-19, pr-25, pr-280, pr-30, pr-32, pr-39, pr-39-review, pr-45, pr-46) — вне scope (гигиена, отдельный bash).
  • run-pipeline/SKILL.md Template D — без изменений (создание pr-M остаётся;
    cleanup переезжает в merge-pr tool).

Closes #50

Closes #50

## Что сделано - `.opencode/tools/merge-pr.ts`: добавлен helper `cleanupPrBranch(prNumber, cwd)` — guard через `git branch --list pr-N` (пустой stdout → skip), `git branch -D pr-N` при наличии, soft fail `⚠️ failed to delete local pr-N branch: <stderr>` при ошибке (НЕ блокирует merge). Вызов добавлен во все return-точки после успешного merge (кроме repo-mismatch path — там локальная очистка пропущена целиком). - `tests/_ts_loader.mjs`: stub `git branch --list` расширен — для target `pr-*` по умолчанию возвращает пустой stdout (существующие тесты не ломаются), override через `GIT_PR_BRANCH_LIST_STUB` (например `"pr-45"`) для тестов pr-N cleanup. - `tests/test_merge_pr_tool.py`: 2 новых теста (`test_merge_pr_cleans_pr_branch`, `test_merge_pr_no_pr_branch`) — покрывают presence/absence pr-N ветки. Существующие 15 тестов без регрессии. - `docs/decisions/098-pr-53-merge-pr-cleanup-pr-branches.md`: ADR с обоснованием fix в `merge-pr.ts` (deterministic, single source of truth) vs `run-pipeline/SKILL.md` Template D manual cleanup (протокольное изменение, race condition, не покрывает PR без fix_ci). ## Почему `run-pipeline/SKILL.md` Template D (fix_ci) создаёт локальные ветки `pr-M` через `git fetch origin pull/M/head:pr-M`, но `merge-pr.ts` чистил только оригинальную feature-ветку (по `headRef`), а `pr-M` не трогал — ветки копились бесконечно. В репо уже накопилось 9 мусорных веток (`pr-19, pr-25, pr-280, ...`). Cleanup в `merge-pr.ts` deterministic: tool вызывается ровно один раз на PR (merge phase), не зависит от того, запускал ли агент fix_ci. ## Watch out - Cleanup ТОЛЬКО `pr-${pr_number}` (текущего PR) — НЕ все `pr-*` ветки (иначе можно удалить pr-N другого PR при parallel work). - `pr-39-review` (не-стандартное имя с суффиксом) НЕ матчится `pr-39` — existing `pr-39-review` ручная мусорная ветка, не удаляется автоматически. - Repo-mismatch path (`worktreeRepo !== full`) → cleanup пропущен целиком (headRef и pr-N) — worktree не принадлежит PR repo. - `GIT_PR_BRANCH_LIST_STUB` по умолчанию пустой — это намеренно, чтобы существующие тесты (не знающие про pr-N cleanup) не видели pr-N ветку и не получали diff в expected messages. Тесты с pr-N cleanup явно выставляют stub. ## Pending - Cleanup существующих 9 мусорных веток (`pr-19, pr-25, pr-280, pr-30, pr-32, pr-39, pr-39-review, pr-45, pr-46`) — вне scope (гигиена, отдельный bash). - `run-pipeline/SKILL.md` Template D — без изменений (создание `pr-M` остаётся; cleanup переезжает в merge-pr tool). Closes #50 Closes #50
docs(adr): add ADR-098 merge-pr pr-N branch cleanup rationale
All checks were successful
CI (always) / bootstrap (pull_request) Successful in 4s
CI / bootstrap (pull_request) Successful in 6s
CI / typecheck (pull_request) Successful in 30s
CI / lint (pull_request) Successful in 30s
CI / complexity (pull_request) Successful in 30s
CI / test (3.13) (pull_request) Successful in 1m39s
dbe58d8293
Author
Owner

Code Review Summary

Чистый, хорошо документированный PR. Добавляет cleanup локальных pr-N веток в merge-pr.ts (deterministic, single source of truth) вместо manual cleanup в run-pipeline/SKILL.md Template D. Soft-fail pattern корректный, 2 теста покрывают presence/absence, ADR с обоснованием.

Positives

  • cleanupPrBranch (merge-pr.ts:20-28) — 8 строк, single responsibility, guard через git branch --list → delete → soft fail. Чистая реализация.
  • Soft-fail pattern — ⚠️ failed to delete local pr-N branch: <stderr> НЕ блокирует merge (merge уже прошёл). Правильный best-effort подход.
  • Repo-mismatch path — early return BEFORE prCleanup computation → cleanup пропущен целиком. Соответствует ADR invariant.
  • Stub isolation (_ts_loader.mjs:394-401) — target.startsWith("pr-") → отдельная ветка с GIT_PR_BRANCH_LIST_STUB, не ломает существующий GIT_BRANCH_LIST_STUB для headRef. Существующие 15 тестов без регрессии.
  • 2 теста — test_merge_pr_cleans_pr_branch (ветка есть → delete вызывается) и test_merge_pr_no_pr_branch (ветки нет → delete НЕ вызывается, guard срабатывает). Покрывают оба пути.
  • ADR-098 — детальное обоснование merge-pr.ts (deterministic, single source of truth, covers PR без fix_ci) vs SKILL.md Template D (протокольное изменение, race condition, не покрывает PR без fix_ci).
  • PR body — все 4 heading'а (Что сделано, Почему, Watch out, Pending) заполнены осмысленно, Watch out описывает edge cases (parallel work, pr-39-review не матчится, repo-mismatch).

Cross-file impact

  • merge-pr.ts (writer) ↔ run-pipeline/SKILL.md Template F (reader, вызывает merge-pr) — change additive (suffix appended to return message), contract не сломан. Paired update не нужен.
  • pipeline-status.py парсит ### Verdict: в PR comments, не return merge-pr — не затронут.

Suggestions (info, not blocking)

  • docs/decisions/098-pr-53-...md — нет newline at EOF (\ No newline at end of file). Cosmetic, non-blocking.

Verdict: APPROVE

## Code Review Summary Чистый, хорошо документированный PR. Добавляет cleanup локальных `pr-N` веток в `merge-pr.ts` (deterministic, single source of truth) вместо manual cleanup в `run-pipeline/SKILL.md` Template D. Soft-fail pattern корректный, 2 теста покрывают presence/absence, ADR с обоснованием. ### Positives - **`cleanupPrBranch` (merge-pr.ts:20-28)** — 8 строк, single responsibility, guard через `git branch --list` → delete → soft fail. Чистая реализация. - **Soft-fail pattern** — `⚠️ failed to delete local pr-N branch: <stderr>` НЕ блокирует merge (merge уже прошёл). Правильный best-effort подход. - **Repo-mismatch path** — early return BEFORE `prCleanup` computation → cleanup пропущен целиком. Соответствует ADR invariant. - **Stub isolation (`_ts_loader.mjs:394-401`)** — `target.startsWith("pr-")` → отдельная ветка с `GIT_PR_BRANCH_LIST_STUB`, не ломает существующий `GIT_BRANCH_LIST_STUB` для headRef. Существующие 15 тестов без регрессии. - **2 теста** — `test_merge_pr_cleans_pr_branch` (ветка есть → delete вызывается) и `test_merge_pr_no_pr_branch` (ветки нет → delete НЕ вызывается, guard срабатывает). Покрывают оба пути. - **ADR-098** — детальное обоснование merge-pr.ts (deterministic, single source of truth, covers PR без fix_ci) vs SKILL.md Template D (протокольное изменение, race condition, не покрывает PR без fix_ci). - **PR body** — все 4 heading'а (`Что сделано`, `Почему`, `Watch out`, `Pending`) заполнены осмысленно, `Watch out` описывает edge cases (parallel work, `pr-39-review` не матчится, repo-mismatch). ### Cross-file impact - `merge-pr.ts` (writer) ↔ `run-pipeline/SKILL.md` Template F (reader, вызывает `merge-pr`) — change additive (suffix appended to return message), contract не сломан. Paired update не нужен. - `pipeline-status.py` парсит `### Verdict:` в PR comments, не return merge-pr — не затронут. ### Suggestions (info, not blocking) - **`docs/decisions/098-pr-53-...md`** — нет newline at EOF (`\ No newline at end of file`). Cosmetic, non-blocking. ### Verdict: APPROVE
slaid098 deleted branch fix/tools/merge-pr-cleanup-pr-branches 2026-08-11 17:54:39 +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!53
No description provided.