feat(merge-pr): delete local PR branch after merge + wrong-repo guard #28
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?
Контекст
Зачем: после мержа PR через merge_pr tool локальная ветка PR остаётся в worktree (Forgejo API удаляет только remote-ветку — delete_branch_after_merge: true), что засоряет
git branchи ломает следующийgit checkout master && git pullна повторных циклах. Вторая проблема — wrong-repo: если opencode открыт в repo-B, а PR #N принадлежит repo-A, merge_pr без repo аргумента пошлёт POST в repo-B → 404 → агент видит «⚠️ merge_pr failed ... HTTP 404» без информации, что PR в другом репо.Контекст:
.opencode/tools/merge-pr.ts(23 строки): вызываетrunGh(["pr","merge",N,"--squash","--delete-branch"], args.repo, { cwd: context.worktree }); после мержа НЕ делает ничего локального..opencode/tools/_shared.ts:resolveForgejoRepo(строки 30-41) —repo ?? git -C cwd config --get remote.origin.url(regex[:/]([^/]+)/([^/]+?)(?:\.git)?$), при status!=0 → null;callForgejoGh(строки 161-177): для "pr merge" → POST/repos/{full}/pulls/{n}/merge{Do:"squash", delete_branch_after_merge:true}, okStatus 200;callForgejo(строки 49-77): не-okStatus →{status:1, stderr:"Forgejo API ... → HTTP <code>: <body>"}. Паттерн GET-запросов уже есть:pipeline-status.py:157— GET/repos/{repo}/pulls/{n};_shared.ts:119— GET/repos/{full}/labels. git в контейнере есть (Dockerfile apt-get install git), worktree смонтирован (docker-compose volume app_data/workspaces:/root/workspace). ПаттернspawnSync("git", [...], {cwd: context.worktree})уже используется в commit.ts, create-pr.ts, memory-save.ts. Plugin tools НЕ проходят через permission.bash (доказано: commit.ts работает при deny на git commit).run-pipeline/SKILL.mdTemplate F (строки 134-147):merge-pr({pr_number: M}); строки 144-145: «Если вернулась ⚠️ merge-pr failed ... → репорт пользователю, STOP (НЕ retry через raw bash...)». В.opencode/opencode.json(строки 337-365) agent tools:merge_pr: falseдля general/reviewer/memory-syncer (main agent наследует all true). Тесты:tests/test_merge_pr_tool.py(184 строки, 8 тестов, запускuv run pytest, стабы черезtests/_ts_loader.mjs: стаб fetch строки 357-370, стаб git remote строки 337-339). TS-тестыtests/test_merge_pr_tool.ts— документационные (bun недоступен).Задача
.opencode/tools/merge-pr.ts+.opencode/tools/_shared.ts:a. Pre-flight перед POST merge: GET
/repos/{full}/pulls/{n}(получитьhead.ref,base.ref) и GET/repos/{full}(default_branch). Если PR не найден (404) → НЕ мержить, вернуть диагностику: «PR #N не найден в (определён из remote origin worktree). Похоже, ты работаешь не в том проекте — PR принадлежит другому репо. Открой opencode в правильном проекте и запусти пайплайн там». Без предложения «передай repo» как решения.b. После успешного мержа — локальная очистка ТОЛЬКО если репо PR == репо worktree (resolveForgejoRepo от context.worktree):
git rev-parse --abbrev-ref HEAD) == head.ref →git checkout <default_branch>→git pull→git branch -D <head.ref>.git branch -D <head.ref>БЕЗ переключения веток.git pullупал (локальные изменения/расхождение) → не падать: сообщить «мерж прошёл, но pull не выполнен: <причина>», ветку PR всё равно удалить.c. Если репо PR != репо worktree → мерж выполняется (если repo передан явно), локальная очистка ПРОПУСКАЕТСЯ с явным сообщением «локальная очистка пропущена: worktree ≠ репо PR ».
d. Финальное сообщение тула: «PR #N merged (squash, remote branch deleted). Local <head.ref> deleted, switched to .» (или варианты без переключения/без удаления — адаптируй под фактически выполненные шаги).
.opencode/skills/run-pipeline/SKILL.mdTemplate F (строки 134-147): при ошибке «PR not found / 404» → STOP + «проверь, в том ли проекте ты работаешь (worktree ≠ репо PR)», НЕ retry с repo аргументом..opencode/opencode.json: обновить description тула merge_pr (упомянуть: pre-flight проверка, удаление локальной ветки PR, wrong-repo guard). Описание тула задаётся в.opencode/tools/merge-pr.ts(description вtool({...})).tests/test_merge_pr_tool.py: добавить тесты: (а) 404 на GET pre-flight → диагностика wrong-repo, POST merge НЕ вызывается; (б) успешный мерж → удаление локальной ветки PR с checkout на default + pull; (в) текущая ветка != ветка PR → удаление без переключения; (г) репо PR != worktree → мерж без локальной очистки + сообщение; (д) локальной ветки нет → пропуск без падения. При необходимости расширить стабы вtests/_ts_loader.mjs(fetch для GET/pulls/{n}и GET/repos/{full}, git команды: rev-parse, branch, checkout, pull). Старые тесты должны остаться зелёными (существующие стабы уже покрывают git remote и rev-parse).Контракты
uv run pytest tests/test_merge_pr_tool.py— все старые + новые тесты зелёныеИнварианты
default_branch), НЕ хардкод mainГраничные случаи
Влияние на связанные компоненты
.opencode/skills/run-pipeline/SKILL.mdTemplate F — парная правка (STOP при 404, без retry с repo).opencode/tools/_shared.ts— новые хелперы (pre-flight, resolveForgejoRepo для сравнения репо) — остальные 3 тула (create-issue, create-pr, post-review) используют runGh, их поведение не меняетсяtests/test_merge_pr_tool.py+tests/_ts_loader.mjs— расширение стабовpipeline-status.py— НЕ меняется (строка 157 GET /pulls/{n} уже существует — паттерн)Вне scope
Критерии приемки
uv run pytest tests/test_merge_pr_tool.pyпроходит (все старые + новые тесты)