diff --git a/.opencode/scripts/pipeline-status.py b/.opencode/scripts/pipeline-status.py index 2854477..0cf6eca 100644 --- a/.opencode/scripts/pipeline-status.py +++ b/.opencode/scripts/pipeline-status.py @@ -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: ', " + "dispatch subagent (subagent_type=general) with prompt 'fix reviewer comments: ', " "commit, push → re-loop (pipeline_status проверит CI автоматически)" ) REVIEW_NEXT_NEEDS_DISCUSSION = "уточни вопросы с автором PR (verdict: NEEDS_DISCUSSION)" diff --git a/.opencode/skills/add-skill/SKILL.md b/.opencode/skills/add-skill/SKILL.md index 4df9f1d..913434d 100644 --- a/.opencode/skills/add-skill/SKILL.md +++ b/.opencode/skills/add-skill/SKILL.md @@ -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)" diff --git a/.opencode/skills/commit/SKILL.md b/.opencode/skills/commit/SKILL.md deleted file mode 100644 index 0318a80..0000000 --- a/.opencode/skills/commit/SKILL.md +++ /dev/null @@ -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`. Новый коммит пишется в том же стиле. diff --git a/.opencode/skills/configure-opencode/SKILL.md b/.opencode/skills/configure-opencode/SKILL.md index 06c5f85..6559e59 100644 --- a/.opencode/skills/configure-opencode/SKILL.md +++ b/.opencode/skills/configure-opencode/SKILL.md @@ -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. Не дублировать блоки между репо diff --git a/.opencode/skills/run-pipeline/SKILL.md b/.opencode/skills/run-pipeline/SKILL.md index 6504b1c..627d85b 100644 --- a/.opencode/skills/run-pipeline/SKILL.md +++ b/.opencode/skills/run-pipeline/SKILL.md @@ -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: ", commit, push → re-loop (`pipeline_status` проверит CI автоматически). \ No newline at end of file +- Если reviewer вердикт `REQUEST_CHANGES` → dispatch subagent (general) с prompt "fix reviewer comments: ", commit, push → re-loop (`pipeline_status` проверит CI автоматически). \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md index 03b31f5..6af02ed 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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--.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] `, 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 diff --git a/docs/decisions/017-pr-42-orchestration-switch.md b/docs/decisions/017-pr-42-orchestration-switch.md new file mode 100644 index 0000000..fa80503 --- /dev/null +++ b/docs/decisions/017-pr-42-orchestration-switch.md @@ -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 = избыточность. \ No newline at end of file diff --git a/docs/handoff/pr-42-orchestration-switch.md b/docs/handoff/pr-42-orchestration-switch.md new file mode 100644 index 0000000..d03bf20 --- /dev/null +++ b/docs/handoff/pr-42-orchestration-switch.md @@ -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). \ No newline at end of file diff --git a/docs/project-map/README.md b/docs/project-map/README.md index 9932fdb..81df356 100644 --- a/docs/project-map/README.md +++ b/docs/project-map/README.md @@ -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 diff --git a/tests/test_pipeline_status.py b/tests/test_pipeline_status.py index b21b271..369c6ea 100644 --- a/tests/test_pipeline_status.py +++ b/tests/test_pipeline_status.py @@ -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): diff --git a/tests/test_pipeline_status_next_actions.py b/tests/test_pipeline_status_next_actions.py new file mode 100644 index 0000000..11c0f4d --- /dev/null +++ b/tests/test_pipeline_status_next_actions.py @@ -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