Files
lda-wf/docs/historical/superpowers/plans/2026-06-29-set-workflow-output.md
T

297 lines
9.2 KiB
Markdown

# Draft Workflow Output Command 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 a first-class command for editing top-level workflow output bindings without forcing agents to write JSON Patch.
**Architecture:** `WorkflowDraft.output` is top-level workflow output projection and uses input-binding shape: `path` reads from graph input/state/context and `target` writes to the public output payload. Add a focused API method that replaces or merges this list, then expose it through RPC, client, CLI, docs, and skills.
**Tech Stack:** Python 3.14, Typer CLI, Pydantic draft models, JSON-RPC transport, pytest.
---
### Task 1: API Method
**Files:**
- Modify: `src/wf_api/drafts.py`
- Modify: `src/wf_api/service.py`
- Modify: `src/wf_api/surface.py`
- Test: `tests/wf_api/test_drafts_service.py`
- [x] **Step 1: Write failing API tests**
Add tests that create a draft with empty `output`, call `set_workflow_output_map`, and assert the stored draft has top-level output bindings:
```python
async def test_set_workflow_output_map_replaces_top_level_output(tmp_path: Path) -> None:
api = _draft_api(tmp_path)
await api.create_draft_workspace(workspace_id="report", draft=_echo_draft())
result = await api.set_workflow_output_map(
workspace_id="report",
revision=1,
output_map={"state.echoed": "message"},
)
assert result["revision"] == 2
fetched = await api.get_draft_workspace(workspace_id="report")
assert fetched["draft"]["output"] == [{"path": "state.echoed", "target": "message"}]
```
Add a second test for merge:
```python
async def test_set_workflow_output_map_merges_top_level_output(tmp_path: Path) -> None:
api = _draft_api(tmp_path)
draft = {**_echo_draft(), "output": [{"path": "state.echoed", "target": "message"}]}
await api.create_draft_workspace(workspace_id="report", draft=draft)
await api.set_workflow_output_map(
workspace_id="report",
revision=1,
output_map={"state.other": "other"},
merge=True,
)
fetched = await api.get_draft_workspace(workspace_id="report")
assert fetched["draft"]["output"] == [
{"path": "state.echoed", "target": "message"},
{"path": "state.other", "target": "other"},
]
```
- [x] **Step 2: Run tests RED**
Run:
```powershell
uv run pytest tests/wf_api/test_drafts_service.py::test_set_workflow_output_map_replaces_top_level_output tests/wf_api/test_drafts_service.py::test_set_workflow_output_map_merges_top_level_output -q
```
Expected: fail because `set_workflow_output_map` does not exist.
- [x] **Step 3: Implement API method**
In `WorkflowDraftApi`, add:
```python
async def set_workflow_output_map(
self,
*,
workspace_id: str,
revision: int,
output_map: dict[str, str],
merge: bool = False,
) -> dict[str, Any]:
if merge:
workspace = self._draft_store().get_workspace(workspace_id)
existing = {
str(binding.get("path")): str(binding.get("target"))
for binding in workspace.draft.get("output", [])
if isinstance(binding, dict)
and isinstance(binding.get("path"), str)
and isinstance(binding.get("target"), str)
}
output_map = {**existing, **output_map}
return await self.patch_draft_workspace(
workspace_id=workspace_id,
revision=revision,
patch=[
{
"op": "replace",
"path": "/output",
"value": [
{"path": source, "target": target}
for source, target in output_map.items()
],
}
],
)
```
Add delegates to `WorkflowApi` and `WorkflowDraftSurface`.
- [x] **Step 4: Run API tests GREEN**
Run the two tests from Step 2. Expected: pass.
- [x] **Step 5: Commit**
```powershell
git add src/wf_api/drafts.py src/wf_api/service.py src/wf_api/surface.py tests/wf_api/test_drafts_service.py
git commit -m "feat: edit workflow output map in draft api"
```
### Task 2: RPC, Client, And CLI
**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`
- Modify: `src/wf_cli/commands/drafts.py`
- Test: `tests/wf_transport_rpc_http/test_app.py`
- Test: `tests/wf_transport_rpc_http/test_client.py`
- Test: `tests/wf_cli/test_remote_target.py`
- Test: `tests/wf_cli/test_app.py`
- [x] **Step 1: Add failing transport and CLI tests**
Add RPC app/client tests that call `workflow.draft_workspaces.set_workflow_output_map` with `{"state.echoed": "message"}`.
Add CLI smoke test:
```python
def test_wf_draft_set_workflow_output_uses_rpc_target(monkeypatch: pytest.MonkeyPatch) -> None:
calls: list[dict[str, object]] = []
class FakeDrafts:
async def set_workflow_output_map(self, **kwargs: object) -> dict[str, object]:
calls.append(kwargs)
return {"workspace_id": "report", "revision": 2}
patch_remote_context(monkeypatch, FakeDrafts())
result = runner.invoke(
app,
[
"--url",
"http://example.test/rpc",
"draft",
"set-workflow-output",
"report",
"--revision",
"1",
"--map",
"state.markdown=markdown",
],
)
assert result.exit_code == 0
assert calls == [
{
"workspace_id": "report",
"revision": 1,
"output_map": {"state.markdown": "markdown"},
"merge": False,
}
]
```
- [x] **Step 2: Run tests RED**
Run:
```powershell
uv run pytest tests/wf_transport_rpc_http/test_app.py::test_rpc_draft_workspace_focused_edit_methods tests/wf_transport_rpc_http/test_client.py::test_rpc_client_draft_workspace_focused_edit_methods tests/wf_cli/test_remote_target.py::test_wf_draft_set_workflow_output_uses_rpc_target -q
```
Expected: fail because DTO/method/command are missing.
- [x] **Step 3: Implement transport and CLI**
Add `SetWorkflowOutputMapParams` with `workspace_id`, `revision`, `output_map`, `merge`.
Register RPC method name:
```python
"workflow.draft_workspaces.set_workflow_output_map"
```
Add client method:
```python
async def set_workflow_output_map(
self, *, workspace_id: str, revision: int, output_map: dict[str, str], merge: bool = False
) -> dict[str, Any]:
return await self._call(
"workflow.draft_workspaces.set_workflow_output_map",
{
"workspace_id": workspace_id,
"revision": revision,
"output_map": output_map,
"merge": merge,
},
)
```
Add CLI:
```python
@app.command("set-workflow-output")
def set_workflow_output(...):
"""Set top-level workflow output projection.
Repeat --map once per mapping. Example:
--map state.markdown=markdown --map state.title=title
"""
```
Use the existing `_parse_map_flags` helper. Add `--merge` with the same replace/merge wording used by `set-input` and `set-output`.
- [x] **Step 4: Run tests GREEN**
Run the tests from Step 2. Expected: pass.
- [x] **Step 5: Commit**
```powershell
git add src/wf_transport_rpc_http src/wf_cli/commands/drafts.py tests/wf_transport_rpc_http tests/wf_cli
git commit -m "feat: expose workflow output draft command"
```
### Task 3: Docs And Skills
**Files:**
- Modify: `docs/wf_cli.md`
- Modify: `docs/workflow_drafts.md`
- Modify: `skills/wf-cli/SKILL.md`
- Modify: `skills/wf-workflow/references/draft-workspaces.md`
- Modify: `docs/current_roadmap.md`
- [x] **Step 1: Document command**
Add example:
```bash
wf draft set-workflow-output report_ws \
--revision 8 \
--map state.markdown=markdown \
--map state.title=title
```
State clearly: this edits top-level `WorkflowDraft.output`; step-level `wf draft set-output` edits one step's node-output-to-state bindings.
- [x] **Step 2: Update skills**
Add a rule:
```md
Use `wf draft set-workflow-output` for final workflow output projection.
Use `wf draft set-output` only for step output bindings.
```
- [x] **Step 3: Verify**
Run:
```powershell
uv run pytest tests/wf_api/test_drafts_service.py tests/wf_cli/test_app.py tests/wf_cli/test_remote_target.py tests/wf_transport_rpc_http/test_app.py tests/wf_transport_rpc_http/test_client.py -q
uv run ruff check src/wf_api src/wf_cli src/wf_transport_rpc_http tests/wf_api tests/wf_cli tests/wf_transport_rpc_http
uv run basedpyright --level error src/wf_api/drafts.py src/wf_cli/commands/drafts.py src/wf_transport_rpc_http tests/wf_api/test_drafts_service.py tests/wf_cli/test_remote_target.py
```
- [x] **Step 4: Commit**
```powershell
git add docs/wf_cli.md docs/workflow_drafts.md skills/wf-cli/SKILL.md skills/wf-workflow/references/draft-workspaces.md docs/current_roadmap.md
git commit -m "docs: document workflow output draft command"
```
---
## Self-Review
- This plan targets one public UX gap: agents should not need JSON Patch for top-level workflow output.
- It does not change step `set-output` semantics.
- It does not attempt automatic schema projection; that belongs to the separate bind/discoverability plan.