diff --git a/.opencode/scripts/pipeline-status.py b/.opencode/scripts/pipeline-status.py index 15bdaed..2854477 100644 --- a/.opencode/scripts/pipeline-status.py +++ b/.opencode/scripts/pipeline-status.py @@ -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", } diff --git a/.opencode/skills/run-pipeline/SKILL.md b/.opencode/skills/run-pipeline/SKILL.md index 45dc77c..6504b1c 100644 --- a/.opencode/skills/run-pipeline/SKILL.md +++ b/.opencode/skills/run-pipeline/SKILL.md @@ -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 ` +3. Создай handoff + ADR: `bash .opencode/scripts/scaffold-handoff.sh M ` (M — будет PR номер, используй placeholder `` в handoff frontmatter, потом исправишь после `gh pr create`). 4. Коммиты в формате `type(scope): description` (≤72 chars, English, no @@ -133,6 +134,21 @@ Log: `gh run view --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 --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: ", commit, push → re-loop (`pipeline_status` проверит CI автоматически). \ No newline at end of file diff --git a/.opencode/tools/merge-pr.ts b/.opencode/tools/merge-pr.ts new file mode 100644 index 0000000..91f6151 --- /dev/null +++ b/.opencode/tools/merge-pr.ts @@ -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).` + }, +}) diff --git a/docs/decisions/010-pr-30-run-pipeline-rewrite.md b/docs/decisions/010-pr-30-run-pipeline-rewrite.md new file mode 100644 index 0000000..29c3992 --- /dev/null +++ b/docs/decisions/010-pr-30-run-pipeline-rewrite.md @@ -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`. \ No newline at end of file diff --git a/docs/handoff/pr-30-run-pipeline-rewrite.md b/docs/handoff/pr-30-run-pipeline-rewrite.md new file mode 100644 index 0000000..9cfc6f6 --- /dev/null +++ b/docs/handoff/pr-30-run-pipeline-rewrite.md @@ -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 Контекст/Альтернативы). \ No newline at end of file diff --git a/docs/project-map/README.md b/docs/project-map/README.md index f9a4254..e2af986 100644 --- a/docs/project-map/README.md +++ b/docs/project-map/README.md @@ -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/ diff --git a/tests/test_pipeline_status.py b/tests/test_pipeline_status.py index 12bde8b..b21b271 100644 --- a/tests/test_pipeline_status.py +++ b/tests/test_pipeline_status.py @@ -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"), ], )