feat: pipeline-driver rewrite + merge_pr tool (#30)

* feat(tools): add merge_pr TS tool wrapper

* refactor(run-pipeline): replace raw gh pr merge with merge_pr tool

* fix(scripts): update pipeline-status MERGE NEXT action

* docs(handoff): add pr-16 handoff + ADR-010

* fix(handoff): remove dangling ADR-012/ADR-016 references

* docs(handoff): set PR number 30

* docs(project-map): add merge-pr.ts tool (PR#30)

---------

Co-authored-by: opencode-agent <agent@slaid098.dev>
This commit is contained in:
Sergey 2026-07-24 00:59:27 +03:00 committed by GitHub
parent 1bd541f172
commit 6911a251d3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 105 additions and 7 deletions

View file

@ -613,7 +613,7 @@ NEXT_ACTIONS: dict[str, str] = {
"DOCS": "запустить docs-reviewer (режим pre-merge)",
"CI": "проверь статус CI вручную (gh run view)",
"REVIEW": "запустить reviewer (task subagent_type=reviewer)",
"MERGE": "смержить PR (gh pr merge N --squash --delete-branch)",
"MERGE": "вызвать merge_pr tool ({pr_number: N})",
"MEMORY": "запустить memory-syncer",
}

View file

@ -26,7 +26,7 @@ description: Автономный исполнитель PR-пайплайна.
- Пропускать вызов `pipeline_status`, даже если «кажется, что фаза уже ✅» — скрипт решает.
- Делать bash `sleep` для ожидания CI — `pipeline_status` сам блокирует до 5 мин (polling Actions API внутри `check_ci`). Один вызов → финальный статус.
- Merge при CI ❌ (transitive guard в скрипте).
- Использовать `--admin` flag для `gh pr merge`.
- Передавать `--admin` flag в `merge_pr` (или raw `gh pr merge`) — никогда.
- Параллелить subagents (последовательно: action → `pipeline_status` → next action).
### Остановы
@ -37,8 +37,9 @@ description: Автономный исполнитель PR-пайплайна.
## Phase 0: Bootstrap
1. Если задача описана в чате, а не issue → load `issue` skill, создай GitHub
issue N (через subagent с `issue` skill, чтобы не засорять контекст).
1. Если задача описана в чате, а не issue → dispatch subagent (general type)
с инструкцией load `issue` skill и создать GitHub issue N. Main agent НЕ
пишет body issue и НЕ запускает `gh issue create` (чистый orchestrator).
2. Запусти subagent (general type) с prompt template A → PR M с `Closes #N` в
body. Subagent вернёт PR номер M.
3. Войди в loop ПРОТОКОЛ выше.
@ -52,7 +53,7 @@ description: Автономный исполнитель PR-пайплайна.
1. Checkout new branch `type/scope/kebab-description` от master.
2. Реализуй по спеке issue (точно, без отклонений). Если спека содержит ошибки,
зафикь и продолжай — не додумывай.
3. Создай handoff + ADR: `bash config/scripts/scaffold-handoff.sh M <slug>`
3. Создай handoff + ADR: `bash .opencode/scripts/scaffold-handoff.sh M <slug>`
(M — будет PR номер, используй placeholder `<PR-NUMBER>` в handoff
frontmatter, потом исправишь после `gh pr create`).
4. Коммиты в формате `type(scope): description` (≤72 chars, English, no
@ -133,6 +134,21 @@ Log: `gh run view <run-id> --log-failed` output:
репорт пользователю (guard от случайного коммита в master).
```
### Template F (merge)
MERGE phase — main agent вызывает tool напрямую (НЕ subagent, НЕ raw bash).
`pipeline_status` сам решает, можно ли мержить (CI gate внутри скрипта —
transitive guard). После `NEXT: ...merge_pr...` → один вызов:
```
merge_pr({ pr_number: M })
```
Если вернулась `⚠️ merge_pr failed ...` → репорт пользователю, STOP (НЕ retry
через raw bash — это нарушит orchestrator-контракт). Если `PR #M merged
successfully ...` → 1 строка прогресса и re-loop (`pipeline_status` покажет
`Status: COMPLETE` или перейдёт на MEMORY phase).
## API Restrictions
Использовать только Actions API (`pipeline_status`, `gh run list`, `gh run view`, `gh api repos/.../actions/runs`). **Запрещено** `gh pr checks` и `gh pr view --json statusCheckRollup` — 403 на fine-grained PAT (scope `Checks: read` не существует).
@ -143,5 +159,6 @@ Log: `gh run view <run-id> --log-failed` output:
- Скрипт read-only (только `gh api`/`gh pr view`, без мутаций).
- После каждой фазы → 1 строка прогресса юзеру.
- Если subagent error → 1 retry, потом STOP + report пользователю.
- `gh pr merge M --squash --delete-branch` (без `--admin`).
- `merge_pr({ pr_number: M })` tool — единственный способ мержить PR (без
`--admin`, без raw bash). См. Template F.
- Если reviewer вердикт `REQUEST_CHANGES` → запусти subagent (general) с prompt "fix reviewer comments: <list>", commit, push → re-loop (`pipeline_status` проверит CI автоматически).

View file

@ -0,0 +1,24 @@
import { spawnSync } from "child_process"
import { tool } from "@opencode-ai/plugin"
export default tool({
description: "Merge a PR via squash + delete branch. Orchestrator-safe wrapper for `gh pr merge N --squash --delete-branch`. Main agent calls this tool instead of raw bash, aligning with the pure-orchestrator model (tool-led philosophy, see ADR-010).",
args: {
pr_number: tool.schema.number().describe("PR number to merge"),
},
async execute(args, context) {
const r = spawnSync("gh", [
"pr", "merge", String(args.pr_number),
"--squash", "--delete-branch",
], {
encoding: "utf-8",
cwd: context.worktree,
})
if (r.status !== 0) {
return `⚠️ merge_pr failed for PR #${args.pr_number} (exit ${r.status}): ${r.stderr || r.stdout}`
}
return `PR #${args.pr_number} merged successfully (squash, branch deleted).`
},
})

View file

@ -0,0 +1,29 @@
# ADR-010: merge_pr tool wrapper for orchestrator model
## Статус
Accepted
## Контекст
MERGE phase выполнялась main agent'ом через raw `gh pr merge` (см. `run-pipeline/SKILL.md` Rules + `pipeline-status.py` `NEXT_ACTIONS["MERGE"]`). Это конфликтует с pure-orchestrator моделью: main agent = plan/delegate/verify, НЕ исполняет mutations напрямую. Tool-led philosophy (обёртка side-effects в tools, imported из приватного репо как принцип без локального ADR) предписывает: (1) сохранить orchestrator-контракт, (2) дать uniform API surface, (3) централизовать guard logic.
Дополнительно: `config/` paths в `run-pipeline/SKILL.md` (`config/scripts/scaffold-handoff.sh`) stale после PR#23 (миграция config/ → .opencode/). Phase 0 "load issue skill" ambiguity — противоречит PR#28 issue-skill rewrite (full subagent delegation).
## Решение
- Created `merge_pr` TS tool (`.opencode/tools/merge-pr.ts`) — wrapper для `gh pr merge N --squash --delete-branch` через `spawnSync`. Built with `@opencode-ai/plugin` `tool({...})` pattern (matching `pipeline-status.ts`, `spec-status.ts`), auto-discovered из `.opencode/tools/` — без регистрации в `opencode.json`.
- `run-pipeline/SKILL.md`: replaced raw `gh pr merge` в Rules → `merge_pr({ pr_number: M })` tool call; added explicit `Template F (merge)` MERGE phase section (tool call, error handling, re-loop).
- `run-pipeline/SKILL.md` Phase 0: "load issue skill" → "dispatch subagent (general) with instruction to load `issue` skill" (pure orchestrator).
- `run-pipeline/SKILL.md` Template A: `config/scripts/scaffold-handoff.sh``.opencode/scripts/scaffold-handoff.sh`.
- `pipeline-status.py` `NEXT_ACTIONS["MERGE"]`: "смержить PR (gh pr merge N...)" → "вызвать merge_pr tool ({pr_number: N})...". Placeholder `N` для `get_next_action` replace.
- Тест `test_get_next_action` MERGE assertion обновлён.
## Альтернативы
- **Dedicated merger subagent** — отклонено. Subagent = task executor с own context, ослабляет security guard (merge by main agent, НЕ subagent — established guard). `merge_pr` tool вызывается main agent'ом напрямую — preserves guard + orchestrator-контракт.
- **Explicit exception в AGENTS.md** ("main agent may run gh pr merge") — отклонено. Violates pure-orchestrator model, создаёт precedent для других raw-bash exceptions.
- **Keep raw bash + документировать** — отклонено. Orchestrator conflict не resolved, tool-led philosophy не honoured.
- **`check-permissions.py` runtime merge guard в tool** — рассмотрено, отложено. `check-permissions.py` сейчас только linting (CI-time), не runtime guard. Реализация runtime guard — follow-up (potential ADR).
## Связанные
- Tool-led philosophy — referenced из приватного репо как принцип (без локального ADR-номера).
- PR#28 (issue-skill full subagent delegation) — Phase 0 ambiguity resolved в том же направлении.
- PR#29 (commands rename: `/pipeline-driver``/run-pipeline`) — tool names (`pipeline_status`, `merge_pr`) independent от command names.
- Issue #10 (AGENTS.md orchestrator rewrite) — pending, references `/run-pipeline`.

View file

@ -0,0 +1,27 @@
# PR: run-pipeline rewrite + merge_pr tool
## Что сделано
- Created merge_pr TS tool (`.opencode/tools/merge-pr.ts`) — orchestrator-safe wrapper for `gh pr merge N --squash --delete-branch`. Built with `@opencode-ai/plugin` `tool({...})` pattern (matching `pipeline-status.ts` / `spec-status.ts`), auto-discovered from `.opencode/tools/` — no `opencode.json` registration needed.
- run-pipeline skill: replaced raw `gh pr merge` with `merge_pr({ pr_number: M })` tool call in Rules + added explicit `Template F (merge)` MERGE phase section.
- Fixed `config/scripts/scaffold-handoff.sh``.opencode/scripts/scaffold-handoff.sh` (Template A, step 3).
- Fixed Phase 0 ambiguity: "load issue skill" → "dispatch subagent (general) with instruction to load `issue` skill and create issue N" (main agent = pure orchestrator, не пишет issue body).
- Updated `pipeline-status.py` `NEXT_ACTIONS["MERGE"]`: "смержить PR (gh pr merge N...)" → "вызвать merge_pr tool ({pr_number: N})" (краткая форма — verbose вариант `— orchestrator-safe wrapper ...` превышал ruff E501 100 chars в тесте `test_get_next_action`; суть в tool call, детали в ADR-010).
- Updated test `test_pipeline_status.py::test_get_next_action` MERGE assertion to new string.
- Reformulated restriction line 29 to keep `--admin` ban but remove actionable raw-bash phrasing.
## Почему
MERGE by main agent via raw bash = orchestrator conflict (main agent = plan only, не исполняет mutations). `merge_pr` tool wrapper aligns with tool-led philosophy (обёртка side-effects в tools для orchestrator-контракта + uniform API + centralized guards). `config/` paths stale after PR#23 migration (config/ → .opencode/). Phase 0 ambiguity resolved in direction of PR#28 issue-skill rewrite (full subagent delegation). Stale "load pipeline-driver before PR" reference — не найдена в файле (переименование в #14 уже очистило); AGENTS.md ещё не существует (#10 не смержен), правки по AGENTS.md пропущены.
## Pending
- AGENTS.md orchestrator rewrite (#10) — references `/run-pipeline` + `/spec` (после rename в #29), collapse Dev Workflow + Pipeline section в pointer.
- `pipeline_status` oracle AMBIGUOUS bug — root cause не изолирован (PR#25-#29 pattern: oracle branch-matching стабильно не детектит runs от `always-ci.yml`). Надёжная альтернатива: `gh pr view N --json statusCheckRollup`.
- `opencode.json` tools section НЕ добавлен — tools auto-discovered из `.opencode/tools/*.ts` через `@opencode-ai/plugin` (проверено по существующим `pipeline-status.ts`, `spec-status.ts`). Issue спека предполагала регистрацию, но это не соответствует фактической конвенции репо.
- `check-permissions.py` merge guard НЕ реализован — `merge_pr` tool не имеет pre-flight security guard. Guard потенциально полезен (subagent deny merge), но `check-permissions.py` сейчас только linting permission rules, не runtime guard. Follow-up.
## Watch out
- `merge_pr` tool = TS wrapper, вызывает `gh pr merge --squash --delete-branch` через `spawnSync`. `cwd = context.worktree` (как в `pipeline-status.ts`).
- `pipeline_status` tool name unchanged (independent от command rename в #29). `merge_pr` — новый tool, экспонирует programmatic API.
- `get_next_action` в `pipeline-status.py` делает `action.replace("N", str(pr_number))` — placeholder в NEXT_ACTIONS должен быть `N` (НЕ `M`), иначе replace не сработает. MERGE action использует `{pr_number: N}`.
- run-pipeline skill renamed from pipeline-driver in #14/#29 (command `/run-pipeline`, skill dir `run-pipeline/`).
- `@opencode-ai/plugin` `tool({...})` pattern — НЕ plain `export default async function` (как в issue спеке). Issue спека показывала старый/упрощённый signature; фактическая конвенция репо — `tool({...})` с `args` schema.
- ADR numbering в этом репо sequential (001..009 на момент PR). ADRs из приватного `slaid098/opencode` (tool-led philosophy, subagent deny merge) НЕ мигрировали — references убраны, оставлен смысл (см. ADR-010 Контекст/Альтернативы).

View file

@ -37,6 +37,7 @@ opencode-config/
│ │ ├── run-tests/SKILL.md # Test runner guide
│ │ └── spec/SKILL.md # 9-phase spec generation
│ ├── tools/
│ │ ├── merge-pr.ts # merge_pr tool wrapper (orchestrator-safe gh pr merge) — PR#30
│ │ ├── pipeline-status.ts # pipeline_status tool wrapper
│ │ └── spec-status.ts # spec_status tool wrapper
│ ├── scripts/

View file

@ -977,7 +977,7 @@ def test_format_pr_row_review_request_changes(monkeypatch):
("DOCS", "запустить docs-reviewer (режим pre-merge)"),
("CI", "проверь статус CI вручную (gh run view)"),
("REVIEW", "запустить reviewer (task subagent_type=reviewer)"),
("MERGE", "смержить PR (gh pr merge 46 --squash --delete-branch)"),
("MERGE", "вызвать merge_pr tool ({pr_number: 46})"),
("MEMORY", "запустить memory-syncer"),
],
)