* 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>
140 lines
5.3 KiB
Python
140 lines
5.3 KiB
Python
"""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
|