29 KiB
Draft Remove Commands Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Add focused draft remove commands for routes, steps, and step bindings so agents can recover from bad draft edits without raw JSON Patch.
Architecture: Implement semantic remove helpers in WorkflowDraftAuthoringApi, delegate through WorkflowApi, and expose the same operations through RPC, MCP, and CLI. Removal persists structurally patchable edits and may return status: "invalid"; save/compile remain strict.
Tech Stack: Python 3.14, Typer, Pydantic, JSON-RPC transport, MCP workflow surface, pytest, Ruff, basedpyright.
File Structure
- Modify
src/wf_api/draft_authoring.py: addremove_draft_route,remove_draft_step, andremove_draft_binding. - Modify
src/wf_api/service.py: add facade delegates. - Modify
src/wf_api/surface.py: add protocol methods. - Modify
src/wf_transport_rpc_http/models.py: add params DTOs. - Modify
src/wf_transport_rpc_http/methods/drafts.py: add JSON-RPC methods. - Modify
src/wf_transport_rpc_http/client/drafts.py: add RPC client methods. - Modify
src/wf_transport_rpc_http/__init__.py: export DTOs if nearby draft DTOs are exported there. - Modify
src/wf_mcp/workflow_surface/models.py: add MCP request models. - Modify
src/wf_mcp/workflow_surface/tools.py: add MCP tools. - Modify
src/wf_cli/commands/drafts.py: addremove-route,remove-step, andremove-binding. - Modify
tests/wf_api/test_drafts_service.py: API behavior tests. - Modify
tests/wf_transport_rpc_http/test_app.py: JSON-RPC method tests. - Modify
tests/wf_transport_rpc_http/test_client.py: RPC client tests. - Modify
tests/wf_mcp/server/test_config.py: MCP tool registration assertions. - Modify
tests/wf_cli/test_app.py: CLI help assertions. - Modify
tests/wf_cli/test_remote_target.py: CLI/RPC routing smoke tests. - Modify
docs/wf_cli.md,skills/wf-cli/SKILL.md, andskills/wf-workflow/references/draft-workspaces.md: public guidance. - Modify
docs/current_roadmap.md: mark completion.
Task 1: API Remove Helpers
Files:
-
Modify:
src/wf_api/draft_authoring.py -
Modify:
src/wf_api/service.py -
Modify:
src/wf_api/surface.py -
Test:
tests/wf_api/test_drafts_service.py -
Step 1: Write API tests
Add these tests to tests/wf_api/test_drafts_service.py near the existing
branch/handle/add-step tests:
@pytest.mark.asyncio
async def test_remove_draft_route_persists_invalid_workspace(tmp_path: Path) -> None:
api = _draft_api(FileWorkflowArtifactStore(tmp_path / "remove_route"))
await api.create_draft_workspace(
workspace_id="route_ws",
draft={
"name": "route_ws",
"start": "echo",
"steps": {
"echo": {"use": "demo.personal.echo_tool", "input": [], "output": []}
},
"routes": {"echo": {"ok": "__end__"}},
"input_schema": {"type": "object", "properties": {}},
"state_schema": {"type": "object", "properties": {}},
"output_schema": {"type": "object", "properties": {}},
},
)
result = await api.remove_draft_route(
workspace_id="route_ws",
revision=1,
step_id="echo",
outcome="ok",
)
assert result["revision"] == 2
assert result["status"] == "invalid"
fetched = await api.get_draft_workspace(
workspace_id="route_ws",
include_draft=True,
)
assert fetched["draft"]["routes"]["echo"] == {}
@pytest.mark.asyncio
async def test_remove_draft_step_removes_outgoing_routes_not_inbound_routes(
tmp_path: Path,
) -> None:
api = _draft_api(FileWorkflowArtifactStore(tmp_path / "remove_step"))
await api.create_draft_workspace(
workspace_id="step_ws",
draft={
"name": "step_ws",
"start": "first",
"steps": {
"first": {"use": "demo.personal.echo_tool", "input": [], "output": []},
"second": {"use": "demo.personal.echo_tool", "input": [], "output": []},
},
"routes": {
"first": {"ok": "second"},
"second": {"ok": "__end__"},
},
"input_schema": {"type": "object", "properties": {}},
"state_schema": {"type": "object", "properties": {}},
"output_schema": {"type": "object", "properties": {}},
},
)
result = await api.remove_draft_step(
workspace_id="step_ws",
revision=1,
step_id="second",
)
assert result["revision"] == 2
assert result["status"] == "invalid"
assert any(
item["code"] == "unknown_edge_destination"
for item in result["diagnostics"]
)
fetched = await api.get_draft_workspace(
workspace_id="step_ws",
include_draft=True,
)
assert "second" not in fetched["draft"]["steps"]
assert "second" not in fetched["draft"]["routes"]
assert fetched["draft"]["routes"]["first"]["ok"] == "second"
@pytest.mark.asyncio
async def test_remove_draft_binding_removes_input_and_output_bindings(
tmp_path: Path,
) -> None:
api = _draft_api(FileWorkflowArtifactStore(tmp_path / "remove_binding"))
await api.create_draft_workspace(
workspace_id="binding_ws",
draft={
"name": "binding_ws",
"start": "echo",
"steps": {
"echo": {
"use": "demo.personal.echo_tool",
"input": [
{"path": "input.message", "target": "message"},
{"path": "input.extra", "target": "extra"},
],
"output": [
{"source": "echoed", "target": "state.echoed"},
{"source": "debug", "target": "state.debug"},
],
}
},
"routes": {"echo": {"ok": "__end__"}},
"input_schema": {"type": "object", "properties": {}},
"state_schema": {"type": "object", "properties": {}},
"output_schema": {"type": "object", "properties": {}},
},
)
result = await api.remove_draft_binding(
workspace_id="binding_ws",
revision=1,
step_id="echo",
inputs=("message",),
outputs=("debug",),
)
assert result["revision"] == 2
fetched = await api.get_draft_workspace(
workspace_id="binding_ws",
include_draft=True,
)
assert fetched["draft"]["steps"]["echo"]["input"] == [
{"path": "input.extra", "target": "extra"}
]
assert fetched["draft"]["steps"]["echo"]["output"] == [
{"source": "echoed", "target": "state.echoed"}
]
@pytest.mark.asyncio
async def test_remove_missing_draft_element_is_noop(tmp_path: Path) -> None:
api = _draft_api(FileWorkflowArtifactStore(tmp_path / "remove_noop"))
await api.create_draft_workspace(
workspace_id="noop_ws",
draft={
"name": "noop_ws",
"start": "echo",
"steps": {
"echo": {"use": "demo.personal.echo_tool", "input": [], "output": []}
},
"routes": {"echo": {"ok": "__end__"}},
"input_schema": {"type": "object", "properties": {}},
"state_schema": {"type": "object", "properties": {}},
"output_schema": {"type": "object", "properties": {}},
},
)
result = await api.remove_draft_route(
workspace_id="noop_ws",
revision=1,
step_id="echo",
outcome="missing",
)
assert result["revision"] == 1
- Step 2: Run tests and verify RED
Run:
uv run pytest tests/wf_api/test_drafts_service.py::test_remove_draft_route_persists_invalid_workspace tests/wf_api/test_drafts_service.py::test_remove_draft_step_removes_outgoing_routes_not_inbound_routes tests/wf_api/test_drafts_service.py::test_remove_draft_binding_removes_input_and_output_bindings tests/wf_api/test_drafts_service.py::test_remove_missing_draft_element_is_noop -q
Expected: failures because the methods do not exist.
- Step 3: Implement API helpers
In src/wf_api/draft_authoring.py, add these methods to
WorkflowDraftAuthoringApi after handle_draft:
async def remove_draft_route(
self,
*,
workspace_id: str,
revision: int,
step_id: str,
outcome: str,
) -> dict[str, Any]:
"""Remove one route; missing routes are revision-checked no-ops."""
workspace = self.drafts._draft_store().get_workspace(workspace_id)
draft_routes = workspace.draft.get("routes", {})
if not isinstance(draft_routes, dict):
raise ValueError("draft routes must be an object")
step_routes = draft_routes.get(step_id, {})
if not isinstance(step_routes, dict):
raise ValueError(f"routes for step {step_id!r} must be an object")
if outcome not in step_routes:
checked = self._workspace_if_revision_matches(
workspace_id=workspace_id,
revision=revision,
)
if isinstance(checked, dict):
return checked
return summarize_draft_workspace(checked)
return await self.drafts.patch_draft_workspace(
workspace_id=workspace_id,
revision=revision,
patch=[
{
"op": "remove",
"path": (
f"/routes/{escape_json_pointer(step_id)}/"
f"{escape_json_pointer(outcome)}"
),
}
],
)
async def remove_draft_step(
self,
*,
workspace_id: str,
revision: int,
step_id: str,
) -> dict[str, Any]:
"""Remove a step and its own route map; inbound routes are left explicit."""
workspace = self.drafts._draft_store().get_workspace(workspace_id)
steps = workspace.draft.get("steps", {})
if not isinstance(steps, dict):
raise ValueError("draft steps must be an object")
if step_id not in steps:
checked = self._workspace_if_revision_matches(
workspace_id=workspace_id,
revision=revision,
)
if isinstance(checked, dict):
return checked
return summarize_draft_workspace(checked)
patch = [
{
"op": "remove",
"path": f"/steps/{escape_json_pointer(step_id)}",
}
]
routes = workspace.draft.get("routes", {})
if isinstance(routes, dict) and step_id in routes:
patch.append(
{
"op": "remove",
"path": f"/routes/{escape_json_pointer(step_id)}",
}
)
return await self.drafts.patch_draft_workspace(
workspace_id=workspace_id,
revision=revision,
patch=patch,
)
async def remove_draft_binding(
self,
*,
workspace_id: str,
revision: int,
step_id: str,
inputs: Sequence[str] = (),
outputs: Sequence[str] = (),
) -> dict[str, Any]:
"""Remove selected local input/output bindings from one draft step."""
if not inputs and not outputs:
raise ValueError("pass at least one input or output binding to remove")
workspace = self.drafts._draft_store().get_workspace(workspace_id)
step = draft_step(workspace.draft, step_id)
current_inputs = step.get("input", [])
current_outputs = step.get("output", [])
if not isinstance(current_inputs, list):
raise ValueError(f"input bindings for step {step_id!r} must be a list")
if not isinstance(current_outputs, list):
raise ValueError(f"output bindings for step {step_id!r} must be a list")
input_targets = set(inputs)
output_sources = set(outputs)
next_inputs = [
item for item in current_inputs if item.get("target") not in input_targets
]
next_outputs = [
item for item in current_outputs if item.get("source") not in output_sources
]
if next_inputs == current_inputs and next_outputs == current_outputs:
checked = self._workspace_if_revision_matches(
workspace_id=workspace_id,
revision=revision,
)
if isinstance(checked, dict):
return checked
return summarize_draft_workspace(checked)
patch: list[dict[str, Any]] = []
if next_inputs != current_inputs:
patch.append(
{
"op": "replace",
"path": f"/steps/{escape_json_pointer(step_id)}/input",
"value": next_inputs,
}
)
if next_outputs != current_outputs:
patch.append(
{
"op": "replace",
"path": f"/steps/{escape_json_pointer(step_id)}/output",
"value": next_outputs,
}
)
return await self.drafts.patch_draft_workspace(
workspace_id=workspace_id,
revision=revision,
patch=patch,
)
If draft_step is not imported in draft_authoring.py, import it from
wf_api.draft_payloads.
- Step 4: Add service and protocol delegates
In src/wf_api/service.py, add methods mirroring branch_draft and
handle_draft:
async def remove_draft_route(
self,
*,
workspace_id: str,
revision: int,
step_id: str,
outcome: str,
) -> dict[str, Any]:
return await self._draft_authoring.remove_draft_route(
workspace_id=workspace_id,
revision=revision,
step_id=step_id,
outcome=outcome,
)
async def remove_draft_step(
self,
*,
workspace_id: str,
revision: int,
step_id: str,
) -> dict[str, Any]:
return await self._draft_authoring.remove_draft_step(
workspace_id=workspace_id,
revision=revision,
step_id=step_id,
)
async def remove_draft_binding(
self,
*,
workspace_id: str,
revision: int,
step_id: str,
inputs: Sequence[str] = (),
outputs: Sequence[str] = (),
) -> dict[str, Any]:
return await self._draft_authoring.remove_draft_binding(
workspace_id=workspace_id,
revision=revision,
step_id=step_id,
inputs=inputs,
outputs=outputs,
)
Import Sequence from collections.abc if it is not already available in
service.py. In src/wf_api/surface.py, add the same async method signatures
to WorkflowDraftSurface.
- Step 5: Run API tests
Run:
uv run pytest tests/wf_api/test_drafts_service.py::test_remove_draft_route_persists_invalid_workspace tests/wf_api/test_drafts_service.py::test_remove_draft_step_removes_outgoing_routes_not_inbound_routes tests/wf_api/test_drafts_service.py::test_remove_draft_binding_removes_input_and_output_bindings tests/wf_api/test_drafts_service.py::test_remove_missing_draft_element_is_noop -q
Expected: PASS.
- Step 6: Commit
git add src/wf_api/draft_authoring.py src/wf_api/service.py src/wf_api/surface.py tests/wf_api/test_drafts_service.py
git commit -m "feat: add draft remove authoring helpers"
Task 2: RPC And Client Surface
Files:
-
Modify:
src/wf_transport_rpc_http/models.py -
Modify:
src/wf_transport_rpc_http/methods/drafts.py -
Modify:
src/wf_transport_rpc_http/client/drafts.py -
Modify:
src/wf_transport_rpc_http/__init__.py -
Test:
tests/wf_transport_rpc_http/test_app.py -
Test:
tests/wf_transport_rpc_http/test_client.py -
Step 1: Add RPC DTOs
In src/wf_transport_rpc_http/models.py, near other draft params, add:
class RemoveDraftRouteParams(RpcParamsModel):
workspace_id: str = Field(min_length=1)
revision: int = Field(ge=1)
step_id: str = Field(min_length=1)
outcome: str = Field(min_length=1)
class RemoveDraftStepParams(RpcParamsModel):
workspace_id: str = Field(min_length=1)
revision: int = Field(ge=1)
step_id: str = Field(min_length=1)
class RemoveDraftBindingParams(RpcParamsModel):
workspace_id: str = Field(min_length=1)
revision: int = Field(ge=1)
step_id: str = Field(min_length=1)
inputs: list[str] = Field(default_factory=list)
outputs: list[str] = Field(default_factory=list)
Export these names from src/wf_transport_rpc_http/__init__.py if adjacent
draft DTOs are exported there.
- Step 2: Register RPC methods
In src/wf_transport_rpc_http/methods/drafts.py, add methods:
workflow.draft_workspaces.remove_route
workflow.draft_workspaces.remove_step
workflow.draft_workspaces.remove_binding
Each method should call the corresponding server.api method and wrap
ValueError, KeyError, LookupError, and FileNotFoundError with
raise_workflow_rpc_error, matching branch and handle.
- Step 3: Add RPC client methods
In src/wf_transport_rpc_http/client/drafts.py, add:
async def remove_draft_route(
self: RpcCaller,
*,
workspace_id: str,
revision: int,
step_id: str,
outcome: str,
) -> dict[str, Any]:
return await self._call(
"workflow.draft_workspaces.remove_route",
{
"workspace_id": workspace_id,
"revision": revision,
"step_id": step_id,
"outcome": outcome,
},
)
async def remove_draft_step(
self: RpcCaller,
*,
workspace_id: str,
revision: int,
step_id: str,
) -> dict[str, Any]:
return await self._call(
"workflow.draft_workspaces.remove_step",
{
"workspace_id": workspace_id,
"revision": revision,
"step_id": step_id,
},
)
async def remove_draft_binding(
self: RpcCaller,
*,
workspace_id: str,
revision: int,
step_id: str,
inputs: list[str] | None = None,
outputs: list[str] | None = None,
) -> dict[str, Any]:
return await self._call(
"workflow.draft_workspaces.remove_binding",
{
"workspace_id": workspace_id,
"revision": revision,
"step_id": step_id,
"inputs": inputs or [],
"outputs": outputs or [],
},
)
Each should call the method names from Step 2.
- Step 4: Add transport tests
In tests/wf_transport_rpc_http/test_app.py, extend the draft focused-edit
test or add a new test that calls workflow.draft_workspaces.remove_route and
asserts the route is absent after inspect.
In tests/wf_transport_rpc_http/test_client.py, add a client test that calls
client.remove_draft_binding(...) and asserts the request method/payload match:
assert calls[-1]["method"] == "workflow.draft_workspaces.remove_binding"
assert calls[-1]["params"]["inputs"] == ["message"]
- Step 5: Run transport tests
Run:
uv run pytest tests/wf_transport_rpc_http/test_app.py tests/wf_transport_rpc_http/test_client.py -q
Expected: PASS.
- Step 6: Commit
git add src/wf_transport_rpc_http tests/wf_transport_rpc_http
git commit -m "feat: expose draft remove helpers over rpc"
Task 3: MCP And CLI Surface
Files:
-
Modify:
src/wf_mcp/workflow_surface/models.py -
Modify:
src/wf_mcp/workflow_surface/tools.py -
Modify:
src/wf_cli/commands/drafts.py -
Test:
tests/wf_mcp/server/test_config.py -
Test:
tests/wf_cli/test_app.py -
Test:
tests/wf_cli/test_remote_target.py -
Step 1: Add MCP request models and tools
In src/wf_mcp/workflow_surface/models.py, add request models equivalent to
the RPC DTOs:
class RemoveDraftRouteRequest(BaseModel):
workspace_id: WorkspaceId
revision: int = Field(ge=1, description="Expected current workspace revision.")
step_id: str = Field(description="Draft step id whose route should be removed.")
outcome: str = Field(description="Outcome label to remove from the step route map.")
class RemoveDraftStepRequest(BaseModel):
workspace_id: WorkspaceId
revision: int = Field(ge=1, description="Expected current workspace revision.")
step_id: str = Field(description="Draft step id to remove.")
class RemoveDraftBindingRequest(BaseModel):
workspace_id: WorkspaceId
revision: int = Field(ge=1, description="Expected current workspace revision.")
step_id: str = Field(description="Draft step id whose bindings should be removed.")
inputs: list[str] = Field(
default_factory=list,
description="Local input target names to remove.",
)
outputs: list[str] = Field(
default_factory=list,
description="Local output source names to remove.",
)
In src/wf_mcp/workflow_surface/tools.py, register tools:
wf.workflow.remove_draft_route
wf.workflow.remove_draft_step
wf.workflow.remove_draft_binding
Use descriptions that state removal may return status: invalid and should be
followed by validation.
- Step 2: Add CLI commands
In src/wf_cli/commands/drafts.py, add:
@app.command("remove-route")
def remove_draft_route(
ctx: typer.Context,
workspace_id: Annotated[str, typer.Argument(help="Draft workspace id.")],
revision: Annotated[
int, typer.Option("--revision", min=1, help="Expected workspace revision.")
],
step: Annotated[str, typer.Option("--step", help="Draft step id.")],
outcome: Annotated[str, typer.Option("--outcome", help="Outcome route to remove.")],
) -> None:
"""Remove one route from a draft step."""
context = load_cli_context(ctx)
emit_json(
run_cli_operation(
context,
context.handlers.remove_draft_route(
workspace_id=workspace_id,
revision=revision,
step_id=step,
outcome=outcome,
),
)
)
@app.command("remove-step")
def remove_draft_step(
ctx: typer.Context,
workspace_id: Annotated[str, typer.Argument(help="Draft workspace id.")],
revision: Annotated[
int, typer.Option("--revision", min=1, help="Expected workspace revision.")
],
step: Annotated[str, typer.Option("--step", help="Draft step id.")],
) -> None:
"""Remove one step and its outgoing draft route map."""
context = load_cli_context(ctx)
emit_json(
run_cli_operation(
context,
context.handlers.remove_draft_step(
workspace_id=workspace_id,
revision=revision,
step_id=step,
),
)
)
@app.command("remove-binding")
def remove_draft_binding(
ctx: typer.Context,
workspace_id: Annotated[str, typer.Argument(help="Draft workspace id.")],
revision: Annotated[
int, typer.Option("--revision", min=1, help="Expected workspace revision.")
],
step: Annotated[str, typer.Option("--step", help="Draft step id.")],
input_name: Annotated[
list[str] | None,
typer.Option("--input", help="Local input target to remove. Repeatable."),
] = None,
output_name: Annotated[
list[str] | None,
typer.Option("--output", help="Local output source to remove. Repeatable."),
] = None,
) -> None:
"""Remove selected input/output bindings from one draft step.
Removal may return status: invalid. Run `wf draft validate` after cleanup.
"""
if not input_name and not output_name:
raise typer.BadParameter("pass at least one --input or --output")
context = load_cli_context(ctx)
emit_json(
run_cli_operation(
context,
context.handlers.remove_draft_binding(
workspace_id=workspace_id,
revision=revision,
step_id=step,
inputs=input_name or [],
outputs=output_name or [],
),
)
)
CLI option shape:
wf draft remove-route WORKSPACE --revision N --step STEP --outcome OUTCOME
wf draft remove-step WORKSPACE --revision N --step STEP
wf draft remove-binding WORKSPACE --revision N --step STEP --input LOCAL --output LOCAL
For remove-binding, accept repeated --input and repeated --output.
Raise typer.BadParameter("pass at least one --input or --output") when both
are empty.
- Step 3: Add tests
In tests/wf_mcp/server/test_config.py, assert the three MCP tool names are
registered.
In tests/wf_cli/test_app.py, assert wf draft remove-binding --help mentions:
--input
--output
status: invalid
In tests/wf_cli/test_remote_target.py, add one RPC routing smoke that invokes:
[
*base_args,
"draft",
"remove-route",
"route_ws",
"--revision",
"1",
"--step",
"call",
"--outcome",
"ok",
]
Assert exit code 0 and JSON revision is advanced when the route existed.
- Step 4: Run MCP/CLI tests
Run:
uv run pytest tests/wf_mcp/server/test_config.py tests/wf_cli/test_app.py tests/wf_cli/test_remote_target.py -q
Expected: PASS, except unrelated pre-existing broad test failures must be reported with names and reasons.
- Step 5: Commit
git add src/wf_mcp src/wf_cli tests/wf_mcp tests/wf_cli
git commit -m "feat: add draft remove cli and mcp tools"
Task 4: Docs, Roadmap, And Final Verification
Files:
-
Modify:
docs/wf_cli.md -
Modify:
skills/wf-cli/SKILL.md -
Modify:
skills/wf-workflow/references/draft-workspaces.md -
Modify:
docs/current_roadmap.md -
Move:
docs/superpowers/plans/2026-06-28-draft-remove-commands.md -
Step 1: Update user docs
In docs/wf_cli.md, under draft workspace commands, add:
### Remove Draft Elements
Use remove commands to back out one bad route, step, or binding without writing
JSON Patch:
```bash
wf draft remove-route report_ws --revision 8 --step extract --outcome ok
wf draft remove-step report_ws --revision 9 --step render
wf draft remove-binding report_ws --revision 10 --step render --input title
wf draft remove-binding report_ws --revision 11 --step render --output markdown
```
Removal may leave the workspace `status: invalid`. That is normal for
intermediate authoring. Run `wf draft validate`, then repair routes or bindings
before saving or compiling.
In skills/wf-cli/SKILL.md, add a rule:
- To undo a bad draft edit, prefer `wf draft remove-route`,
`wf draft remove-step`, or `wf draft remove-binding` over JSON Patch.
In skills/wf-workflow/references/draft-workspaces.md, add:
Remove commands are for recovery. They do not delete schema fields and
`remove-step` does not remove inbound routes. Validate after removal and repair
the resulting diagnostics explicitly.
- Step 2: Update roadmap
Add a completed bullet under Priority 1:
- Completed: draft workspaces expose focused remove commands for routes, steps,
and step bindings so agents can recover from bad edits without raw JSON Patch.
- Step 3: Archive this plan
Move this file to:
docs/historical/superpowers/plans/2026-06-28-draft-remove-commands.md
- Step 4: Run final verification
Run:
uv run pytest tests/wf_api/test_drafts_service.py tests/wf_transport_rpc_http/test_app.py tests/wf_transport_rpc_http/test_client.py tests/wf_mcp/server/test_config.py tests/wf_cli/test_app.py tests/wf_cli/test_remote_target.py tests/docs -q
uv run ruff check src/wf_api/draft_authoring.py src/wf_transport_rpc_http src/wf_mcp/workflow_surface src/wf_cli/commands/drafts.py tests/wf_api/test_drafts_service.py tests/wf_cli/test_remote_target.py
uv run ruff format --check src/wf_api/draft_authoring.py src/wf_transport_rpc_http src/wf_mcp/workflow_surface src/wf_cli/commands/drafts.py tests/wf_api/test_drafts_service.py tests/wf_cli/test_remote_target.py
uv run basedpyright --level error src/wf_api/draft_authoring.py src/wf_transport_rpc_http src/wf_mcp/workflow_surface src/wf_cli/commands/drafts.py tests/wf_api/test_drafts_service.py tests/wf_cli/test_remote_target.py
Expected: all focused tests pass. Document any unrelated pre-existing failures by test name and reason.
- Step 5: Commit
git add docs/wf_cli.md skills/wf-cli/SKILL.md skills/wf-workflow/references/draft-workspaces.md docs/current_roadmap.md docs/historical/superpowers/plans/2026-06-28-draft-remove-commands.md
git commit -m "docs: document draft remove commands"
Self-Review
- Spec coverage: route removal, step removal, binding removal, no-op behavior, invalid intermediate semantics, docs, and all public surfaces are covered.
- Placeholder scan: no TODO/TBD/fill-in placeholders remain.
- Type consistency: method names use
remove_draft_*; CLI names useremove-*; RPC method names useworkflow.draft_workspaces.remove_*.