fix(tools): cleanup local pr-N branches in merge-pr tool #50
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Контекст
Зачем: fix_ci фаза run-pipeline создаёт локальные ветки
pr-M(черезgit fetch origin pull/M/head:pr-M && git checkout pr-M), ноmerge-pr.tsпосле merge чистит только оригинальную feature-ветку (поheadRef), аpr-Mне трогает → локальные веткиpr-Nкопятся бесконечно. В репо уже 9 мусорных веток:pr-19, pr-25, pr-280, pr-30, pr-32, pr-39, pr-39-review, pr-45, pr-46.Контекст:
run-pipeline/SKILL.md:97(Template D, fix_ci) инструктирует subagent'а:git fetch origin pull/M/head:pr-M && git checkout pr-M— git-native способ checkout PR безgh pr checkout(后者 запрещён/API restrictions в Forgejo-режиме). Это создаёт локальную веткуpr-M.merge-pr.tsпосле merge делает локальную очистку:git branch -D headRef(гдеheadRef— оригинальное имя feature-ветки PR, напримерfix/skills/curl-max-time). Ноpr-MНЕ совпадает сheadRef—merge-pr.tsеё не трогает. Каждый fix_ci-цикл оставляет локальнуюpr-Mнавсегда.Это баг утечки (не фича). Намеренный механизм —
pr-Mдля fix_ci; утечка — отсутствие очистки после merge.Задача
.opencode/tools/merge-pr.tsв блоке локальной очистки (послеgh pr merge --delete-branchи существующегоgit branch -D headRef) добавить удалениеpr-${pr_number}:const prBranch = "pr-" + args.pr_number(или черезpr-${pr_number}template literal).const listRes = spawnSync("git", ["-C", cwd, "branch", "--list", prBranch], { encoding: "utf-8", timeout: 5000 })— guard: еслиlistRes.stdout.trim()пустой → ветки нет, skip. Если есть →git branch -D prBranch.spawnSyncerror/timeout — аналогично существующим git-вызовам вmerge-pr.ts(soft fail с⚠️, НЕ блокировать merge — ветка почистится手动 или при следующем clone).test_merge_pr_cleans_pr_branchвtests/test_merge_pr_tool.py— stubspawnSyncдля:git branch --list pr-45→ stdoutpr-45\n(ветка существует) → следующийgit branch -D pr-45→ exit 0. Expect: merge success +pr-45удалена (stub verified called).test_merge_pr_no_pr_branch— stubgit branch --list pr-46→ stdout empty (ветки нет) →git branch -DНЕ вызывается. Expect: merge success, skip cleanup.NNN-pr-<this_pr>-merge-pr-cleanup-pr-branches.md(номер определи черезls docs/decisions/) — обосновать: утечкаpr-Nветок, fix вmerge-pr.ts(auto-cleanup при merge) vsrun-pipeline/SKILL.mdTemplate D (manual cleanup после fix_ci). Выбратьmerge-pr.ts— deterministic, не требует правки SKILL.md протокола.Контракты
merge-pr({ pr_number: M })— без изменений в signature/args.pr-${pr_number}если существует (git branch --listnon-empty).pr-${pr_number}не существует → skip (no error).git branch -Dfail → soft fail⚠️ failed to delete local pr-M branch: <stderr>(НЕ блокировать merge — основной merge уже прошёл).headRefcleanup (existing behavior) — без изменений.Инварианты
pr-${pr_number}(НЕpr-Nдля других PR, НЕ всеpr-*ветки — только текущего PR).git branch --list pr-Mguard обязателен —git branch -Dна несуществующей ветке упадёт с error.git branch -Derror — merge уже прошёл, cleanup — best-effort.pr-Mветки — их нет на remote (pr-Mчисто локальные, созданныеgit fetch ... :pr-M).Граничные случаи
pr-Mлокальной ветки нет →git branch --listempty → skip (no error).pr-Mлокальная ветка есть →git branch -D pr-M→ cleanup success.pr-Mуже удалена вручную →git branch --listempty → skip.git branch -Dfail (ветка checked out сейчас — невозможный case, мы уже сделалиgit checkout defaultBranchв existing cleanup) → soft fail⚠️, merge success.pr-39-review(не-стандартное имя с суффиксом) — НЕ матчитсяpr-${pr_number}(этоpr-39-review, неpr-39). Existingpr-39-review— ручная мусорная ветка, не удаляется автоматически (толькоpr-39если PR #39 был fix_ci). Cleanup существующих 9 веток — отдельный bash (гигиена, не PR scope).Влияние на связанные компоненты
run-pipeline/SKILL.mdTemplate D (fix_ci, line 97) — без изменений (созданиеpr-Mчерезgit fetch ... :pr-Mостаётся; cleanup переезжает вmerge-pr.ts).run-pipeline/SKILL.mdTemplate F (merge, callsmerge-pr) — без изменений (agent вызываетmerge-pr({pr_number: M}), tool сам делает cleanup).commit.ts/create-pr.ts— без изменений.tests/test_merge_pr_tool.py— 2 новых теста (см. Задача).tests/_ts_loader.mjs— возможно обновить stub еслиspawnSync("git", ["branch", ...])не покрывается существующим GIT stub (проверить, скорее всего уже работает через generic git stub).Вне scope
pr-19, pr-25, pr-280, pr-30, pr-32, pr-39, pr-39-review, pr-45, pr-46) — локальный bashgit branch -D ..., не PR scope (гигиена).run-pipeline/SKILL.mdTemplate D — созданиеpr-Mостаётся (нужно для fix_ci). Cleanup переезжает в merge-pr tool.pr-*старее N дней — over-engineering, не нужно.Критерии приемки
pr-Mсуществует) →merge-prудаляетpr-Mавтоматическиpr-Mнет) →merge-prskip cleanup (no error)git branch -Dfail → soft fail⚠️, merge success (не блокируется)headRefcleanup (existing) — без регрессииtest_merge_pr_tool.pyпроходятtests/test_merge_pr_tool.pyне ломаютсяruff format --check . && ruff check . && mypy src/— green (на самомmerge-pr.ts)