diff --git a/roboco/mcp/flow_server.py b/roboco/mcp/flow_server.py index 2c663f77..e0b0395e 100644 --- a/roboco/mcp/flow_server.py +++ b/roboco/mcp/flow_server.py @@ -210,9 +210,28 @@ def give_me_work() -> dict[str, Any]: return _post(_role_path("give_me_work"), {}) -def i_will_work_on(task_id: str, plan: str | None = None) -> dict[str, Any]: - """Claim/start/recover a task. Works for pending, claimed, needs_revision.""" - return _post(_role_path("i_will_work_on"), {"task_id": task_id, "plan": plan}) +def i_will_work_on( + task_id: str, + plan: str | None = None, + steps: list[dict[str, str]] | None = None, +) -> dict[str, Any]: + """Claim/start/recover a task. Works for pending, claimed, needs_revision. + + Args: + task_id: UUID of the task you are claiming. + plan: One-paragraph narrative of how you'll execute the task. + steps: Ordered execution checklist — list of + ``{"title": "...", "description": "..."}``. Required on a + FRESH claim: the gateway's ``_dev_steps_gate`` rejects an + empty or thin list, and the same list is reused as the + progress checklist (#173 — completing a step advances %). + Re-entry / recovery claims (already-claimed or + needs_revision) do not need steps re-supplied. + """ + return _post( + _role_path("i_will_work_on"), + {"task_id": task_id, "plan": plan, "steps": steps or []}, + ) def open_pr(task_id: str) -> dict[str, Any]: diff --git a/tests/unit/mcp_servers/test_flow_server.py b/tests/unit/mcp_servers/test_flow_server.py index 17a427d2..7da5ebf3 100644 --- a/tests/unit/mcp_servers/test_flow_server.py +++ b/tests/unit/mcp_servers/test_flow_server.py @@ -140,7 +140,11 @@ def test_i_will_work_on_passes_plan(flow_module: types.ModuleType) -> None: flow_module.i_will_work_on("task-uuid", plan="my plan") args, kwargs = fake_client.post.call_args - assert kwargs["json"] == {"task_id": "task-uuid", "plan": "my plan"} + assert kwargs["json"] == { + "task_id": "task-uuid", + "plan": "my plan", + "steps": [], + } assert "/api/v2/flow/developer/i_will_work_on" in args[0] @@ -151,7 +155,28 @@ def test_i_will_work_on_plan_defaults_to_none(flow_module: types.ModuleType) -> flow_module.i_will_work_on("task-uuid") _, kwargs = fake_client.post.call_args - assert kwargs["json"] == {"task_id": "task-uuid", "plan": None} + assert kwargs["json"] == {"task_id": "task-uuid", "plan": None, "steps": []} + + +def test_i_will_work_on_passes_steps(flow_module: types.ModuleType) -> None: + """#172: the MCP tool must forward the steps checklist; the + server-side _dev_steps_gate rejects a fresh claim without it, so a + tool that drops steps wedges every code task.""" + fake_client = _make_fake_client({"status": "in_progress"}) + steps = [ + {"title": "Checkout", "description": "create branch and read README"}, + {"title": "Edit", "description": "append the timestamp comment"}, + ] + + with patch("httpx.Client", return_value=fake_client): + flow_module.i_will_work_on("task-uuid", plan="p", steps=steps) + + _, kwargs = fake_client.post.call_args + assert kwargs["json"] == { + "task_id": "task-uuid", + "plan": "p", + "steps": steps, + } def test_i_am_done_sends_task_id_and_notes(flow_module: types.ModuleType) -> None: