"""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