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:
parent
1bd541f172
commit
6911a251d3
7 changed files with 105 additions and 7 deletions
|
|
@ -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",
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 автоматически).
|
||||
24
.opencode/tools/merge-pr.ts
Normal file
24
.opencode/tools/merge-pr.ts
Normal 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).`
|
||||
},
|
||||
})
|
||||
29
docs/decisions/010-pr-30-run-pipeline-rewrite.md
Normal file
29
docs/decisions/010-pr-30-run-pipeline-rewrite.md
Normal 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`.
|
||||
27
docs/handoff/pr-30-run-pipeline-rewrite.md
Normal file
27
docs/handoff/pr-30-run-pipeline-rewrite.md
Normal 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 Контекст/Альтернативы).
|
||||
|
|
@ -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/
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
],
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue