Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 48 additions & 12 deletions orchestrator/routes/pipelines.py
Original file line number Diff line number Diff line change
Expand Up @@ -11105,11 +11105,12 @@ def _build_phase_prompt(
elif phase == "plan":
lines.extend(
[
"Create a detailed implementation plan.",
"",
"**CRITICAL CONSTRAINT — One Issue = One Workflow = One PR.**",
"All tasks belong to a single pull request. Use phases and commits to",
"organise the work within that PR — do NOT propose multiple PRs.",
"Create a detailed implementation plan, decomposing the work into "
"slices per the slice-DAG guidance at the end of this section. The "
"implement-phase pipeline ships each slice as its own stacked PR. "
"A single-slice plan is fine when the work is cohesive; multi-slice "
"plans are required when an upstream refine-phase work-decomposition "
"HITL decision selected a multi-slice shape.",
"",
"Steps:",
"1. Review any prior analysis",
Expand Down Expand Up @@ -11166,13 +11167,45 @@ def _build_phase_prompt(
"",
*_YAML_TASKS_SAFETY_GUIDANCE,
"",
"Do NOT use a `pr_plan` key or propose multiple PRs.",
"Do NOT use a `pr_plan` key — slice packaging is owned by the "
"slice-DAG section below, not by an ad-hoc PR list.",
"",
"The `test_plan` field is **required** — describe both automated test "
"coverage and any manual verification steps. The `manual_steps` field "
"should list any pre-merge or post-merge actions required by the reviewer "
"or deployer; use an empty string if none.",
"",
# ----------------------------------------------------
# #2137 — slice-DAG planner guidance (mirrors the
# concurrent task_planner block; keep the two paths
# aligned so a refine-phase multi-slice HITL decision
# is honoured regardless of which planner runs).
# ----------------------------------------------------
"## Slice-DAG guidance (#2137)",
"",
"The implement-phase pipeline ships each plan **slice** (formerly "
"**phase**) as its own stacked PR. The plan you emit drives that "
"DAG; the rules below are mandatory.",
"",
"**Yaml key swap**: prefer the canonical ``slices:`` key in your "
"``# yaml-tasks`` block (the parser also accepts ``phases:`` for "
"backward compatibility). New plans should use ``slices:``.",
"",
"**Slice-sizing guidance (soft, advisory only)**: target ≤1,000 "
"LOC per slice where possible. The plan reviewer flags oversized "
"slices as advisory but does NOT reject on size.",
"",
"**Forest constraint (HARD)**: every slice must have at most ONE "
"DAG parent — the implement-phase pipeline ships every slice as a "
"stacked PR with exactly one base branch. Multi-parent slices "
"break the stacking invariant and are rejected at plan ingestion.",
"",
"**Auto-serialization rule for would-be multi-parent slices**: "
"when a slice would naturally have >1 parents, serialise the "
"upstream slices into a linear chain and record the chosen "
"ordering on the downstream slice's ``serialized_chain_order`` "
"field.",
"",
f"Write your plan to `{plan_path}`.",
"Commit and push the draft when done.",
"",
Expand Down Expand Up @@ -13031,11 +13064,13 @@ def _build_agent_prompt(
draft_path = _get_draft_path("plan", issue_number=issue_number, pipeline_id=pipeline_id)
lines.extend(
[
"Decompose the architecture analysis into a single-PR implementation plan.",
"",
"**CRITICAL CONSTRAINT — One Issue = One Workflow = One PR.**",
"All tasks belong to a single pull request. Use phases and commits to",
"organise the work within that PR — do NOT propose multiple PRs.",
"Decompose the architecture analysis into a slice-DAG implementation "
"plan. The implement-phase pipeline ships each slice as its own "
"stacked PR — see the ``## Slice-DAG guidance (#2137)`` section below "
"for the slice-shaping rules. A single-slice plan is fine when the "
"work is cohesive; multi-slice plans are required when an upstream "
"refine-phase work-decomposition HITL decision selected a "
"multi-slice shape.",
"",
"Steps:",
"1. Review the architecture analysis from the ARCHITECT agent",
Expand Down Expand Up @@ -13094,7 +13129,8 @@ def _build_agent_prompt(
"",
*_YAML_TASKS_SAFETY_GUIDANCE,
"",
"Do NOT use a `pr_plan` key or propose multiple PRs.",
"Do NOT use a `pr_plan` key — slice packaging is owned by the "
"slice-DAG section below, not by an ad-hoc PR list.",
"",
"The `test_plan` field is **required** — describe both automated test "
"coverage and any manual verification steps. The `manual_steps` field "
Expand Down
87 changes: 87 additions & 0 deletions orchestrator/tests/test_pipeline_prompts.py
Original file line number Diff line number Diff line change
Expand Up @@ -3289,6 +3289,93 @@ def test_decomposition_guidance_lives_outside_template_fence(self):
)


class TestPlannerPromptSliceDagFraming:
"""Planner prompts must not contradict slice-DAG decomposition (#2601).

Both planner paths — ``_build_phase_prompt(phase="plan")`` (sequential)
and ``_build_agent_prompt(role_value="task_planner")`` (concurrent) —
used to open with ``CRITICAL CONSTRAINT — One Issue = One Workflow =
One PR`` and a follow-on ``do NOT propose multiple PRs`` line. That
directly contradicts the slice-DAG guidance the concurrent path already
carried and silently turned multi-slice refine-phase HITL decisions
into dead letters. This class is a negative-regression suite: the
opener must not return, and the slice-DAG framing must remain.
"""

@staticmethod
def _sequential_plan_prompt() -> str:
return _build_phase_prompt(
phase="plan",
pipeline_id="test-pipe",
pipeline_mode="issue",
prompt="Implement the change.",
issue_number=100,
)

@staticmethod
def _concurrent_planner_prompt() -> str:
return _build_agent_prompt(
role_value="task_planner",
phase="plan",
pipeline_id="test-pipe",
pipeline_mode="issue",
prompt="Implement the change.",
issue_number=100,
concurrent=True,
)

def test_sequential_plan_drops_one_pr_opener(self):
prompt = self._sequential_plan_prompt()
for needle in (
"CRITICAL CONSTRAINT",
"One Issue = One Workflow = One PR",
"do NOT propose multiple PRs",
):
assert needle not in prompt, (
f"sequential planner still carries removed opener {needle!r} — "
"this contradicts slice-DAG decomposition (#2601)"
)

def test_concurrent_planner_drops_one_pr_opener(self):
prompt = self._concurrent_planner_prompt()
for needle in (
"CRITICAL CONSTRAINT",
"One Issue = One Workflow = One PR",
"do NOT propose multiple PRs",
):
assert needle not in prompt, (
f"concurrent planner still carries removed opener {needle!r} — "
"this contradicts slice-DAG decomposition (#2601)"
)

def test_sequential_plan_includes_slice_dag_guidance(self):
prompt = self._sequential_plan_prompt()
for needle in (
"Slice-DAG guidance (#2137)",
"stacked PR",
"Forest constraint",
"serialized_chain_order",
):
assert needle in prompt, (
f"sequential planner missing slice-DAG token {needle!r} — "
"the two planner paths must stay aligned (#2601)"
)

def test_concurrent_planner_includes_slice_dag_guidance(self):
prompt = self._concurrent_planner_prompt()
for needle in (
"Slice-DAG guidance (#2137)",
"stacked PR",
"Forest constraint",
"serialized_chain_order",
):
assert needle in prompt, (
f"concurrent planner missing slice-DAG token {needle!r} — "
"removing the One-PR opener must not have dropped the "
"slice-DAG block (#2601)"
)


class TestReviewerBrcPreamble:
"""Tests that reviewer agents receive BRC preamble in concurrent mode."""

Expand Down
Loading