refactor(pipeline): make pipeline-status sole orchestrator + slim docs (#42)

* refactor(pipeline): add subagent_type and template to NEXT actions

NEXT_ACTIONS now explicitly specifies subagent_type + template per phase (except MERGE which calls merge_pr tool). ISSUE/IMPLEMENT -> general/A, DOCS -> docs-reviewer/B, REVIEW -> reviewer/C, MEMORY -> memory-syncer/E. Existing tests updated to new format, 25 new tests added.

* refactor(docs): slim AGENTS.md and run-pipeline skill

AGENTS.md 71 -> 31 lines: remove Commits/Pull Requests sections (formats in tools), shorten Development Workflow/Pipeline to 1-2 lines. run-pipeline SKILL.md 164 -> 155 lines: remove Phase 0 Bootstrap (duplicated phases, now in pipeline-status NEXT_ACTIONS). Keep protocol, templates, restrictions.

* chore(skills): remove commit skill (format in tool validator)

commit/SKILL.md deleted (format validated by commit.ts tool, PR#38). References updated: configure-opencode SKILL.md (2 places), add-skill SKILL.md (1 place + tree structure). project-map README: remove commit/SKILL.md from tree, add configure-opencode.

* docs(handoff): add ADR-017 and handoff for orchestration switch

PR number in handoff frontmatter is <PR-NUMBER> placeholder, will be set after gh pr create.

* docs(handoff): set PR number

Set PR number 42 in handoff frontmatter, rename handoff/ADR files from pr-41 to pr-42 (pipeline-status matches pr-{pr_number}-*.md in diff).

* docs: update project map + handoff + ADR

---------

Co-authored-by: opencode-agent <agent@slaid098.dev>
This commit is contained in:
Sergey 2026-07-24 15:40:20 +03:00 committed by GitHub
parent f9a9e0f854
commit 67174b4879
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 245 additions and 133 deletions

View file

@ -608,17 +608,17 @@ def get_pr_title(pr_number: int) -> str:
NEXT_ACTIONS: dict[str, str] = {
"ISSUE": "создать issue и связать через Closes #N в body PR",
"IMPLEMENT": "добавить handoff docs/handoff/pr-N-slug.md в diff",
"DOCS": "запустить docs-reviewer (режим pre-merge)",
"ISSUE": "dispatch subagent (subagent_type=general, template=A) for PR #N",
"IMPLEMENT": "dispatch subagent (subagent_type=general, template=A) for PR #N",
"DOCS": "dispatch subagent (subagent_type=docs-reviewer, template=B) for PR #N",
"CI": "проверь статус CI вручную (gh run view)",
"REVIEW": "запустить reviewer (task subagent_type=reviewer)",
"MERGE": "вызвать merge_pr tool ({pr_number: N})",
"MEMORY": "запустить memory-syncer",
"REVIEW": "dispatch subagent (subagent_type=reviewer, template=C) for PR #N",
"MERGE": "call merge_pr tool with pr_number=N",
"MEMORY": "dispatch subagent (subagent_type=memory-syncer, template=E) for PR #N",
}
REVIEW_NEXT_REQUEST_CHANGES = (
"запусти fix subagent (general) с prompt 'fix reviewer comments: <list>', "
"dispatch subagent (subagent_type=general) with prompt 'fix reviewer comments: <list>', "
"commit, push → re-loop (pipeline_status проверит CI автоматически)"
)
REVIEW_NEXT_NEEDS_DISCUSSION = "уточни вопросы с автором PR (verdict: NEEDS_DISCUSSION)"

View file

@ -43,7 +43,7 @@ description: <когда загружать. Триггеры на русско
├── add-skill/SKILL.md ← этот скилл
├── branch/SKILL.md
├── code-standards/SKILL.md
├── commit/SKILL.md
├── configure-opencode/SKILL.md
├── get-project-map/SKILL.md
├── issue/SKILL.md
├── memory/SKILL.md
@ -62,7 +62,7 @@ Opencode сканирует все под-директории `.opencode/skills
## 4. Commit и Push (для implementing agent)
Implementing agent (subagent, которому делегировано создание) после создания файла — сразу коммит и пуш по правилам скилла `commit`:
Implementing agent (subagent, которому делегировано создание) после создания файла — сразу коммит и пуш по правилам `commit` tool (формат валидируется tool'ом):
```bash
cd "$(git rev-parse --show-toplevel)"

View file

@ -1,48 +0,0 @@
---
name: commit
description: Analyse git history of any project and suggest commit messages that match existing conventions.
---
## Процесс
1. Запустить `git log --oneline -20`
2. Если коммиты есть:
- Извлечь все уникальные `type(scope):` паттерны
- Составить список реальных scopes проекта
- Следовать найденному стилю
3. Если коммитов нет (новый проект):
- Базовый формат: `type(scope): description`
- Типы: `feat | fix | chore | docs | refactor | test | style | perf`
- Scope по умолчанию: спросить пользователя
- Description — что сделано (кратко), на английском
- Без точки в конце
- Max ≤72 символа
- Язык: English only — type, scope, description всё на английском
## Type
| Type | When |
|---|---|
| `feat` | New feature |
| `fix` | Bug fix |
| `chore` | Maintenance, cleanup, dependencies, config |
| `refactor` | Code restructuring, no behavior change |
| `docs` | Documentation files (AGENTS.md, SKILL.md, README, handoff, ADR, project-map, docs/) |
| `test` | Adding or fixing tests |
| `style` | Formatting, linting, whitespace only |
| `perf` | Performance improvements |
## Branch naming
`type/scope/description` — kebab-case, из тех же scopes.
## Пример вывода для агента
Если `git log` показывает:
```
chore(config): add docker proxy
fix(docker): resolve no-sandbox
feat(agents.md): add guidelines
```
То scopes: `config`, `docker`, `agents.md`. Новый коммит пишется в том же стиле.

View file

@ -18,7 +18,7 @@ description: Use when adding, changing, or removing MCP servers, providers, perm
## 2. Применение изменений
- `commit` + `push` в репо `slaid098/opencode-config` (через `commit` skill).
- `commit` + `push` в репо `slaid098/opencode-config` (через `commit` tool).
- В клонах: `git pull` + рестарт opencode (MCP-серверы, skills, agents грузятся при старте — см. `add-skill/SKILL.md`). До рестарта правки не видны.
- Для Docker-сетапа: `git pull` на хосте + `docker compose restart opencode` (или эквивалент) — MCP/skills/agents грузятся при старте контейнера.
@ -106,7 +106,7 @@ description: Use when adding, changing, or removing MCP servers, providers, perm
## 8. Commit message
- Формат: `feat(config): ...` / `chore(config): ...` / `fix(config): ...` (conventional commits, English, ≤72 chars).
- Перед commit — загрузить `commit` skill, проверить `git log --oneline -20`, match existing style.
- Перед commit — использовать `commit` tool (валидация формата встроена), проверить `git log --oneline -20`, match existing style.
- Примеры: `feat(config): add integrations MCP server`, `fix(config): correct timeout for integrations discover tool`.
## 9. Не дублировать блоки между репо

View file

@ -6,7 +6,8 @@ description: Автономный исполнитель PR-пайплайна.
# Run Pipeline
Автономная процедура-loop для проведения PR через 7 фаз. Source of truth для
порядка и действий — `pipeline_status` tool.
порядка и действий — `pipeline_status` tool. NEXT action из tool явно
указывает `subagent_type` + `template` (кроме MERGE — `merge_pr` tool).
## ПРОТОКОЛ (ЖЁСТКО)
@ -35,15 +36,6 @@ description: Автономный исполнитель PR-пайплайна.
- `AMBIGUOUS` в выводе `pipeline_status` → STOP + report.
- 5 итераций подряд без прогресса (та же фаза ❌) → STOP + report.
## Phase 0: Bootstrap
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 ПРОТОКОЛ выше.
## Prompt templates
### Template A (implement_issue)
@ -161,4 +153,4 @@ successfully ...` → 1 строка прогресса и re-loop (`pipeline_st
- Если subagent error → 1 retry, потом STOP + report пользователю.
- `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 автоматически).
- Если reviewer вердикт `REQUEST_CHANGES`dispatch subagent (general) с prompt "fix reviewer comments: <list>", commit, push → re-loop (`pipeline_status` проверит CI автоматически).

View file

@ -8,52 +8,13 @@
- Pipeline: каждую фазу (ISSUE → IMPLEMENT → DOCS → CI → REVIEW → MERGE → MEMORY) делегировать subagent'у.
- Subagent error → 1 retry, потом STOP + report.
## Commits
- Language: English only — type, scope, and description all in English
- Format: `type(scope): description` — ≤72 chars
- Types: `feat`, `fix`, `chore`, `docs`, `refactor`, `test`, `style`, `perf`
- Before ANY commit: load `commit` skill (`skill("commit")`), run `git log --oneline -20`, match existing style
- Break large changes into multiple short commits by logical parts
- No period at end, no body unless necessary
## Pull Requests
- Title: на английском, формат `type(scope): what changed` — same principle as commits
- Body: на русском, в Markdown — сначала **что** сделано, затем **почему** (мотивация, контекст)
- Reference issues if applicable
- Before creating PR: load `commit` skill, inspect `git diff` from base branch
## Development Workflow
All PR work runs through `/run-pipeline` (7 phases: ISSUE → IMPLEMENT → DOCS → CI → REVIEW → MERGE → MEMORY). Load the `run-pipeline` skill for the protocol.
1. **Plan** — discuss requirements in chat, understand scope
2. **Issue** — create self-contained GitHub issue (full context, file list, exact content, acceptance criteria, dependencies). Load `issue` skill. Via subagent.
3. **Subagent** — delegate to `task` subagent (general type):
- Reads issue via `gh issue view N`
- Branches off default branch, implements per spec, commits, pushes, creates PR with `Closes #N`
- Creates handoff `docs/handoff/pr-<N>-<slug>.md` and ADR if architectural decision
- Follows issue spec exactly — if spec has errors, report them, don't deviate
4. **Review** — run docs-reviewer (`@docs-reviewer`, pre-merge) then reviewer (`@reviewer`):
- docs-reviewer: updates project map + validates/fixes handoff + ADR, commits to PR branch
- reviewer: posts `## Code Review Summary` comment with verdict APPROVE|REQUEST_CHANGES (does NOT merge)
5. **Merge or Repeat**:
- APPROVE → `merge_pr` tool (orchestrator-safe, via `run-pipeline` skill, after CI ✅)
- Issues found → fix subagent (same branch, new commit) → re-loop → merge
- REQUEST_CHANGES → fix subagent → re-review → merge
6. **Memory-sync** — run memory-syncer (`@memory-syncer`):
- Distills gotchas + ADR pointers from merged handoff into `app_data/opencode-memory/repos/{host}/{org}/{repo}.md`
- Format: `- [YYYY-MM-DD, PR#N] <summary>`, receipt always (even if empty)
- Calls `memory_save`, then guards against accidental commits to main repo
All PR work runs through `/run-pipeline`. Pipeline phases orchestrated by `pipeline_status` tool (NEXT action per phase). Load `run-pipeline` skill.
## Pipeline
Основной pipeline для любой задачи — `/run-pipeline` в UI opencode. Skill выполняет 7 фаз (ISSUE → IMPLEMENT → DOCS → CI → REVIEW → MERGE → MEMORY) автономно, не импровизируя порядок. `merge_pr` tool — после CI ✅ (transitive guard в `pipeline-status.py`).
- Tool `pipeline_status` — read-only oracle, возвращает статус + NEXT action.
- Tool `merge_pr` — orchestrator-safe merge wrapper (replaces raw `gh pr merge`).
- Execution — через `/run-pipeline` (command `.opencode/commands/run-pipeline.md`).
`pipeline_status` = read-only oracle (returns NEXT action). `merge_pr` = orchestrator-safe merge wrapper. Execution via `/run-pipeline` skill.
## Read Path

View file

@ -0,0 +1,24 @@
# ADR-017: pipeline-status sole orchestrator + slim docs
## Статус
Accepted (2026-07-24)
## Контекст
PR #38 (build) добавил 3 детерминированных tool'а (`commit`, `create_pr`, `create_issue`) с валидацией форматов. PR #40 (lock) заблокировал прямые bash-вызовы мутаций через global deny rules + role-based tool access. Но промпты и оркестрация всё ещё ссылались на старые паттерны: `pipeline-status.py` NEXT_ACTIONS описывал действие текстом без указания `subagent_type`/`template`, run-pipeline skill дублировал 7 фаз в `Phase 0: Bootstrap`, AGENTS.md дублировал правила форматов commits/PRs (уже в tools), commit skill дублировал правила коммитов (уже в `commit.ts`).
Нужно: сделать `pipeline-status.py` единственным source of truth для оркестрации (NEXT action явно указывает `subagent_type` + `template`), убрать дублирование в AGENTS.md/run-pipeline skill, удалить commit skill (формат в `commit.ts`).
## Решение
1. **`pipeline-status.py` NEXT_ACTIONS** — каждая фаза явно указывает `subagent_type` + `template` (кроме MERGE — `merge_pr` tool): ISSUE/IMPLEMENT → `subagent_type=general, template=A`; DOCS → `subagent_type=docs-reviewer, template=B`; REVIEW → `subagent_type=reviewer, template=C`; MERGE → `call merge_pr tool with pr_number=N`; MEMORY → `subagent_type=memory-syncer, template=E`. `REVIEW_NEXT_REQUEST_CHANGES``dispatch subagent (subagent_type=general)`. Формат `NEXT:` строки сохранён (pipeline-driver парсит без изменений).
2. **AGENTS.md slim** (71 → 31 строк) — убраны `## Commits` и `## Pull Requests` (форматы в tools). `## Development Workflow` и `## Pipeline` сокращены до 1-2 строк (ссылки на `/run-pipeline` + `pipeline_status`/`merge_pr` tools). Оставлены: Orchestrator Model, Read Path, Code Style, Language.
3. **run-pipeline SKILL.md slim** (164 → 155 строк) — убран `Phase 0: Bootstrap` (дублировал фазы). Оставлены: ПРОТОКОЛ, ЗАПРЕЩЕНО, Остановы, Prompt templates A-F, API Restrictions, Rules.
4. **commit skill удалён** — формат зашит в `commit.ts` валидаторе (PR#38). Ссылки в `configure-opencode/SKILL.md` и `add-skill/SKILL.md` обновлены на `commit` tool.
## Альтернативы
- **Оставить NEXT_ACTIONS описательным текстом** — отклонено: run-pipeline skill должен парсить NEXT action и диспетчизировать subagent'ов. Без явного `subagent_type`/`template` skill должен был гадать или хардкодить маппинг фаза→subagent. Явное указание = single source of truth, deterministic.
- **Не сокращать AGENTS.md/run-pipeline** — отклонено: дублирование правил форматов (AGENTS.md commits/PRs vs tools) и фаз (run-pipeline Phase 0 vs pipeline-status NEXT_ACTIONS) нарушает DRY и pure-orchestrator model. Tools уже валидируют форматы — текстовые правила в AGENTS.md не enforced.
- **Оставить commit skill как pointer на tool** — отклонено: `commit.ts` уже содержит все правила валидации (regex, English, ≤72, staged check). Skill с текстом правил = дублирование. Если правила изменятся — нужно править 2 места. Удаление skill устраняет дублирование.
- **Добавить отдельный tool для dispatch subagent** — отклонено: opencode уже имеет `task` subagent dispatch mechanism. NEXT action строка достаточно для run-pipeline skill чтобы вызвать `task` с правильным `subagent_type` и template. Новый tool = избыточность.

View file

@ -0,0 +1,37 @@
---
pr: 42
title: pipeline-status sole orchestrator + slim docs
---
# PR: pipeline-status sole orchestrator + slim docs
## Что сделано
- `.opencode/scripts/pipeline-status.py``NEXT_ACTIONS`: каждая фаза теперь явно указывает `subagent_type` + `template` (кроме MERGE — `merge_pr` tool). ISSUE/IMPLEMENT → `subagent_type=general, template=A`; DOCS → `subagent_type=docs-reviewer, template=B`; REVIEW → `subagent_type=reviewer, template=C`; MERGE → `call merge_pr tool with pr_number=N` (не subagent); MEMORY → `subagent_type=memory-syncer, template=E`. `REVIEW_NEXT_REQUEST_CHANGES``dispatch subagent (subagent_type=general)`. Формат `NEXT:` строки сохранён (pipeline-driver парсит без изменений) — изменился только текст action.
- `AGENTS.md` сокращён с 71 → 31 строк: убраны `## Commits` и `## Pull Requests` (форматы валидируются `commit`/`create_pr` tools, PR#38). `## Development Workflow` и `## Pipeline` сокращены до 1-2 строк (ссылки на `/run-pipeline` skill + `pipeline_status`/`merge_pr` tools). Оставлены без изменений: `## Orchestrator Model`, `## Read Path`, `## Code Style`, `## Language`.
- `.opencode/skills/run-pipeline/SKILL.md` сокращён с 164 → 155 строк: убран `## Phase 0: Bootstrap` (8 строк, дублировал фазы — теперь порядок в `pipeline-status.py` NEXT_ACTIONS). Оставлены: ПРОТОКОЛ, ЗАПРЕЩЕНО, Остановы, Prompt templates A-F, API Restrictions, Rules. `## Rules` последняя строка обновлена: `запусти fix subagent``dispatch subagent (general)`.
- `.opencode/skills/commit/SKILL.md` удалён (директория `.opencode/skills/commit/` удалена). Формат коммитов зашит в `commit.ts` валидаторе (PR#38).
- Ссылки на commit skill обновлены: `configure-opencode/SKILL.md` (2 места: "через `commit` skill" → "через `commit` tool", "загрузить `commit` skill" → "использовать `commit` tool"), `add-skill/SKILL.md` (1 место: "по правилам скилла `commit`" → "по правилам `commit` tool" + убран `commit/SKILL.md` из дерева структуры skills, добавлен `configure-opencode/SKILL.md`).
- `docs/project-map/README.md`: убран `commit/SKILL.md` из дерева skills, добавлен `configure-opencode/SKILL.md` (был вне алфавитного порядка), обновлено описание `AGENTS.md` ("commits, PRs" → "pipeline, formats enforced by tools").
- `tests/test_pipeline_status.py`: обновлены 7 существующих тестов под новый формат NEXT_ACTIONS — `test_get_next_action` (parametrize: 7 фаз), `test_get_next_action_review` (5 cases), `test_format_single_pr_review_*` (3 теста), `test_format_single_pr_memory_not_done`, `test_format_table_with_prs`, `test_format_pr_row_review_request_changes`. Строки assertions изменены: `запустить reviewer``dispatch subagent (subagent_type=reviewer`, `запустить memory-syncer``dispatch subagent (subagent_type=memory-syncer`, и т.д.
- `tests/test_pipeline_status_next_actions.py` (новый, 25 тестов): для каждой фазы NEXT action содержит `subagent_type` + `template` (кроме MERGE — `merge_pr`); ISSUE/IMPLEMENT → general/A, DOCS → docs-reviewer/B, REVIEW → reviewer/C, MERGE → merge_pr (не subagent), MEMORY → memory-syncer/E; pr_number substitution; REVIEW verdict branching (REQUEST_CHANGES → general, default → reviewer, NEEDS_DISCUSSION → no subagent).
- ADR-017 + этот handoff
## Почему
Третий PR из серии из 3 (build → lock → **switch**). PR #38 (build) добавил 3 детерминированных tool'а (`commit`, `create_pr`, `create_issue`) с валидацией форматов. PR #40 (lock) заблокировал прямые bash-вызовы `git commit`/`gh pr create`/`gh pr merge`/`gh issue create` через global deny rules + role-based tool access. Этот PR (switch) обновляет промпты и оркестрацию на использование tools — `pipeline-status.py` становится единственным source of truth для оркестрации (NEXT action явно указывает `subagent_type` + `template`), а AGENTS.md/run-pipeline skill сокращаются убирая дублирование.
`pipeline-status.py` NEXT_ACTIONS был описательным текстом ("создать issue", "запустить docs-reviewer") без указания какой subagent и какой template использовать. run-pipeline skill дублировал 7 фаз в `Phase 0: Bootstrap`. AGENTS.md дублировал правила форматов commits/PRs (уже зашиты в tools, PR#38). commit skill дублировал правила коммитов (уже в `commit.ts`, PR#38). Цель: single source of truth + убрать дублирование.
Спека issue #41 не содержала ошибок. Все acceptance criteria выполнены.
## Pending
- `run-pipeline/SKILL.md` сокращён с 164 → 155 строк (спека просила ~90). Дальнейшее сокращение невозможно без удаления обязательных секций: спека явно требует оставить ПРОТОКОЛ + ЗАПРЕЩЕНО + Остановы + Prompt templates A-F + API Restrictions + Rules. Одни templates A-F = ~100 строк. Единственное дублирование фаз (`Phase 0: Bootstrap`) удалено.
- `AGENTS.md` = 31 строк (спека просила ~25). 4 оставленные секции (Orchestrator Model 6 строк, Read Path 2, Code Style 5, Language 2) + 2 сокращённые (Development Workflow 2, Pipeline 2) + заголовки = 31. Дальнейшее сокращение потребует удаления обязательных секций.
- `add-skill/SKILL.md` дерево skills теперь не содержит `commit/SKILL.md` и содержит `configure-opencode/SKILL.md` (раньше был вне алфавитного порядка в дереве)
## Watch out
- **NEXT_ACTIONS format совместим с pipeline-driver**: `get_next_action` заменяет `N` на pr_number через `.replace("N", str(pr_number))`. Новые строки содержат `PR #N` и `pr_number=N`оба корректно заменяются. `REVIEW_NEXT_DEFAULT = NEXT_ACTIONS["REVIEW"]` — default branch `get_next_action_review` теперь возвращает `dispatch subagent (subagent_type=reviewer, template=C) for PR #N`. pipeline-driver парсит `NEXT:` строку целиком — изменений в формате строки нет, изменился только текст action.
- **7 существующих тестов обновлены**: `test_get_next_action`, `test_get_next_action_review`, `test_format_single_pr_review_not_done`, `test_format_single_pr_review_request_changes`, `test_format_single_pr_review_needs_discussion`, `test_format_single_pr_memory_not_done`, `test_format_table_with_prs`, `test_format_pr_row_review_request_changes`. Не удалены, а обновлены под новый формат — это часть реализации (формат изменился).
- **commit skill удалён полностью** (директория `.opencode/skills/commit/`). `commit.ts` tool (PR#38) валидирует формат независимо. Skills auto-discovered при старте opencode — после merge нужен `git pull` + рестарт контейнера (config bind-mount, skills грузятся при старте).
- **Ссылки на commit skill обновлены в 3 файлах**: `configure-opencode/SKILL.md` (2 места), `add-skill/SKILL.md` (1 место + дерево). `opencode.json` `commit: true/false` — это tool access (не skill), без изменений. В handoff/ADR других PR упоминания commit skill оставлены как исторические (не актуальны).
- ADR number = sequential (017), НЕ PR number. Проверить ADR naming в handoff до push (эволюция паттерна PR#26 docs-reviewer typo).
- PR number = 42 (установлен после `gh pr create`, отдельный коммит `docs(handoff): set PR number`). Handoff/ADR файлы переименованы с `pr-41-*``pr-42-*` (pipeline-status ищет `pr-{pr_number}-*.md` в diff, pr_number=42).

View file

@ -2,7 +2,7 @@
opencode-config — Docker-based AI coding assistant with persistent memory (opencode configuration). Runs in Docker via `docker-compose.yml` (dind + opencode services).
Root `AGENTS.md` — orchestrator directive (chat = plan only, all via subagents) + global rules (commits, PRs, code style, language RU). Auto-loaded for project + bind-mounted globally in container — PR#31.
Root `AGENTS.md` — orchestrator directive (chat = plan only, all via subagents) + global rules (pipeline, code style, language RU). Formats enforced by tools (commit/create_pr/create_issue). Auto-loaded for project + bind-mounted globally in container — PR#31.
## Structure
@ -27,11 +27,10 @@ opencode-config/
│ │ ├── add-skill/SKILL.md # Create new opencode skill
│ │ ├── branch/SKILL.md # Branch naming conventions
│ │ ├── code-standards/SKILL.md # Universal code style rules
│ │ ├── commit/SKILL.md # Commit message conventions
│ │ ├── configure-opencode/SKILL.md # Canonical rule: write to .opencode/
│ │ ├── get-project-map/SKILL.md # Maintain docs/project-map/
│ │ ├── issue/SKILL.md # GitHub issue creation
│ │ ├── memory/SKILL.md # opencode-memory usage guide
│ │ ├── configure-opencode/SKILL.md # Canonical rule: write to .opencode/
│ │ ├── run-pipeline/SKILL.md # 7-phase pipeline orchestration
│ │ ├── python-development/SKILL.md # Python dev patterns
│ │ ├── release/SKILL.md # Tag + GitHub Release
@ -51,7 +50,7 @@ opencode-config/
│ │ ├── check-adr-refs.py # ADR cross-reference validator (adr-check.yml)
│ │ ├── check-permissions.py # Permissions validator (permissions-check.yml)
│ │ ├── observability.py # OTel spans for tools
│ │ ├── pipeline-status.py # 7-phase oracle (gh PR + CI polling)
│ │ ├── pipeline-status.py # 7-phase oracle (gh PR + CI polling, NEXT_ACTIONS with subagent_type+template) — PR#42
│ │ ├── scaffold-handoff.sh # Scaffold handoff + ADR stubs
│ │ ├── setup-memory.sh # opencode-memory bootstrap (deterministic 6-step flow, idempotent) — PR#36
│ │ ├── spec-status.py # 9-phase spec oracle
@ -91,6 +90,7 @@ opencode-config/
│ ├── test_pipeline_status.py # .opencode/scripts/pipeline-status.py (REVIEW verdict branching)
│ ├── test_pipeline_status_adr.py
│ ├── test_pipeline_status_ci.py
│ ├── test_pipeline_status_next_actions.py # NEXT_ACTIONS subagent_type+template per phase (25 tests) — PR#42
│ ├── test_pipeline_status_tool.py
│ ├── test_pipeline_status_tool.ts # TS wrapper test (mjs loader)
│ ├── test_search.py # src/memory/search.py

View file

@ -851,7 +851,7 @@ def test_format_single_pr_review_not_done(monkeypatch):
ps, "run_cmd", mock_run_cmd({("gh", "pr", "view"): (0, '{"title": "test"}', "")})
)
output = ps.format_single_pr(46, results)
assert "NEXT: запустить reviewer" in output
assert "NEXT: dispatch subagent (subagent_type=reviewer" in output
assert "" in output
@ -870,9 +870,9 @@ def test_format_single_pr_review_request_changes(monkeypatch):
ps, "run_cmd", mock_run_cmd({("gh", "pr", "view"): (0, '{"title": "test"}', "")})
)
output = ps.format_single_pr(46, results)
assert "NEXT: запусти fix subagent" in output
assert "NEXT: dispatch subagent (subagent_type=general)" in output
assert "re-loop" in output
assert "запустить reviewer" not in output.split("NEXT:")[1]
assert "dispatch subagent (subagent_type=reviewer" not in output.split("NEXT:")[1]
def test_format_single_pr_review_needs_discussion(monkeypatch):
@ -891,7 +891,7 @@ def test_format_single_pr_review_needs_discussion(monkeypatch):
)
output = ps.format_single_pr(46, results)
assert "NEXT: уточни вопросы" in output
assert "запустить reviewer" not in output.split("NEXT:")[1]
assert "dispatch subagent (subagent_type=reviewer" not in output.split("NEXT:")[1]
def test_format_single_pr_memory_not_done(monkeypatch):
@ -908,7 +908,7 @@ def test_format_single_pr_memory_not_done(monkeypatch):
ps, "run_cmd", mock_run_cmd({("gh", "pr", "view"): (0, '{"title": "test"}', "")})
)
output = ps.format_single_pr(46, results)
assert "NEXT: запустить memory-syncer" in output
assert "NEXT: dispatch subagent (subagent_type=memory-syncer" in output
def test_format_single_pr_ambiguous(monkeypatch):
@ -945,7 +945,7 @@ def test_format_table_with_prs(monkeypatch):
monkeypatch.setattr(ps, "get_pr_title", lambda n: "test PR title")
output = ps.format_table([47])
assert "PR#47" in output
assert "NEXT: запустить docs-reviewer (режим pre-merge)" in output
assert "NEXT: dispatch subagent (subagent_type=docs-reviewer" in output
def test_format_pr_row_review_request_changes(monkeypatch):
@ -962,8 +962,8 @@ def test_format_pr_row_review_request_changes(monkeypatch):
monkeypatch.setattr(ps, "run_all_checks", lambda n: results)
monkeypatch.setattr(ps, "get_pr_title", lambda n: "test")
output = ps.format_pr_row(47)
assert "NEXT: запусти fix subagent" in output
assert "запустить reviewer" not in output.split("NEXT:")[1]
assert "NEXT: dispatch subagent (subagent_type=general)" in output
assert "dispatch subagent (subagent_type=reviewer" not in output.split("NEXT:")[1]
# ── get_next_action ──────────────────────────────────────────────────────────
@ -972,13 +972,13 @@ def test_format_pr_row_review_request_changes(monkeypatch):
@pytest.mark.parametrize(
("phase", "expected"),
[
("ISSUE", "создать issue и связать через Closes #46 в body PR"),
("IMPLEMENT", "добавить handoff docs/handoff/pr-46-slug.md в diff"),
("DOCS", "запустить docs-reviewer (режим pre-merge)"),
("ISSUE", "dispatch subagent (subagent_type=general, template=A) for PR #46"),
("IMPLEMENT", "dispatch subagent (subagent_type=general, template=A) for PR #46"),
("DOCS", "dispatch subagent (subagent_type=docs-reviewer, template=B) for PR #46"),
("CI", "проверь статус CI вручную (gh run view)"),
("REVIEW", "запустить reviewer (task subagent_type=reviewer)"),
("MERGE", "вызвать merge_pr tool ({pr_number: 46})"),
("MEMORY", "запустить memory-syncer"),
("REVIEW", "dispatch subagent (subagent_type=reviewer, template=C) for PR #46"),
("MERGE", "call merge_pr tool with pr_number=46"),
("MEMORY", "dispatch subagent (subagent_type=memory-syncer, template=E) for PR #46"),
],
)
def test_get_next_action(phase, expected):
@ -988,11 +988,17 @@ def test_get_next_action(phase, expected):
@pytest.mark.parametrize(
("detail", "expected_substring"),
[
("последний verdict reviewer'а: REQUEST_CHANGES", "запусти fix subagent"),
(
"последний verdict reviewer'а: REQUEST_CHANGES",
"dispatch subagent (subagent_type=general)",
),
("последний verdict reviewer'а: NEEDS_DISCUSSION", "уточни вопросы с автором"),
("Code Review Summary не найден в комментариях", "запустить reviewer"),
("APPROVE не найден", "запустить reviewer"),
("нет комментариев PR", "запустить reviewer"),
(
"Code Review Summary не найден в комментариях",
"dispatch subagent (subagent_type=reviewer",
),
("APPROVE не найден", "dispatch subagent (subagent_type=reviewer"),
("нет комментариев PR", "dispatch subagent (subagent_type=reviewer"),
],
)
def test_get_next_action_review(detail, expected_substring):

View file

@ -0,0 +1,140 @@
"""Tests for .opencode/scripts/pipeline-status.py — NEXT_ACTIONS orchestration.
Verifies each phase's NEXT action explicitly specifies ``subagent_type`` and
``template`` (except MERGE which calls ``merge_pr`` tool directly). This makes
``pipeline_status`` the single source of truth for orchestration the
``run-pipeline`` skill parses the ``NEXT:`` line and dispatches accordingly.
"""
import importlib.util
import sys
from pathlib import Path
import pytest
SCRIPT_PATH = (
Path(__file__).resolve().parent.parent / ".opencode" / "scripts" / "pipeline-status.py"
)
spec = importlib.util.spec_from_file_location("pipeline_status_next_actions", SCRIPT_PATH)
ps = importlib.util.module_from_spec(spec)
sys.modules["pipeline_status_next_actions"] = ps
spec.loader.exec_module(ps)
# ── NEXT_ACTIONS: subagent_type + template per phase ─────────────────────────
@pytest.mark.parametrize(
"phase",
["ISSUE", "IMPLEMENT"],
)
def test_next_action_general_subagent_template_a(phase):
"""ISSUE/IMPLEMENT → subagent_type=general, template=A."""
action = ps.get_next_action(phase, 41)
assert "subagent_type=general" in action
assert "template=A" in action
assert "PR #41" in action
def test_next_action_docs_subagent_template_b():
"""DOCS → subagent_type=docs-reviewer, template=B."""
action = ps.get_next_action("DOCS", 41)
assert "subagent_type=docs-reviewer" in action
assert "template=B" in action
assert "PR #41" in action
def test_next_action_review_subagent_template_c():
"""REVIEW → subagent_type=reviewer, template=C."""
action = ps.get_next_action("REVIEW", 41)
assert "subagent_type=reviewer" in action
assert "template=C" in action
assert "PR #41" in action
def test_next_action_merge_pr_tool_not_subagent():
"""MERGE → merge_pr tool (NOT a subagent dispatch)."""
action = ps.get_next_action("MERGE", 41)
assert "merge_pr" in action
assert "pr_number=41" in action
assert "subagent_type" not in action
assert "template=" not in action
def test_next_action_memory_subagent_template_e():
"""MEMORY → subagent_type=memory-syncer, template=E."""
action = ps.get_next_action("MEMORY", 41)
assert "subagent_type=memory-syncer" in action
assert "template=E" in action
assert "PR #41" in action
# ── NEXT_ACTIONS: every dispatchable phase has subagent_type ──────────────────
DISPATCH_PHASES = ["ISSUE", "IMPLEMENT", "DOCS", "REVIEW", "MEMORY"]
@pytest.mark.parametrize("phase", DISPATCH_PHASES)
def test_dispatch_phases_have_subagent_type(phase):
"""All dispatch phases (except MERGE) include subagent_type in NEXT action."""
action = ps.get_next_action(phase, 41)
assert "subagent_type=" in action, f"{phase} NEXT action missing subagent_type: {action}"
@pytest.mark.parametrize("phase", DISPATCH_PHASES)
def test_dispatch_phases_have_template(phase):
"""All dispatch phases (except MERGE) include template=X in NEXT action."""
action = ps.get_next_action(phase, 41)
assert "template=" in action, f"{phase} NEXT action missing template: {action}"
def test_merge_phase_does_not_dispatch_subagent():
"""MERGE is a tool call, not a subagent dispatch — no subagent_type/template."""
action = ps.get_next_action("MERGE", 41)
assert "subagent_type" not in action
assert "template=" not in action
assert "merge_pr" in action
# ── NEXT_ACTIONS: pr_number substitution ──────────────────────────────────────
@pytest.mark.parametrize(
("phase", "pr_number"),
[("ISSUE", 1), ("DOCS", 999), ("REVIEW", 42), ("MERGE", 7), ("MEMORY", 100)],
)
def test_next_action_pr_number_substitution(phase, pr_number):
"""``N`` placeholder in NEXT_ACTIONS is replaced with the actual PR number."""
action = ps.get_next_action(phase, pr_number)
assert str(pr_number) in action
# The literal "N" placeholder must not survive substitution.
assert " PR #N" not in action
assert "pr_number=N" not in action
# ── REVIEW verdict branching still uses subagent_type ─────────────────────────
def test_review_request_changes_dispatches_general_subagent():
"""REVIEW REQUEST_CHANGES → fix subagent (general), not re-run reviewer."""
result = ps.PhaseResult(ps.PhaseStatus.NOT_DONE, "verdict: REQUEST_CHANGES")
action = ps.get_next_action_review(result)
assert "subagent_type=general" in action
assert "fix reviewer comments" in action
def test_review_default_dispatches_reviewer_subagent():
"""REVIEW default (no verdict) → dispatch reviewer subagent (template C)."""
result = ps.PhaseResult(ps.PhaseStatus.NOT_DONE, "нет комментариев PR")
action = ps.get_next_action_review(result)
assert "subagent_type=reviewer" in action
assert "template=C" in action
def test_review_needs_discussion_no_subagent():
"""REVIEW NEEDS_DISCUSSION → clarification, not a subagent dispatch."""
result = ps.PhaseResult(ps.PhaseStatus.NOT_DONE, "verdict: NEEDS_DISCUSSION")
action = ps.get_next_action_review(result)
assert "уточни вопросы" in action
assert "subagent_type" not in action