docs: archive draft CLI diagnostics plan

This commit is contained in:
lda
2026-06-29 22:18:43 +07:00 Verified
parent d72911d490
commit 36bae57863
@@ -0,0 +1,456 @@
# Draft CLI Diagnostics Polish 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:** Reduce agent retries by rejecting `set-input` local-target syntax mistakes early, adding actionable route-outcome repair guidance, and pinning repeated `bind` behavior.
**Architecture:** Keep each diagnostic at the layer with enough information to explain it correctly. The Typer adapter can identify `local.x` as an invalid `set-input` target before loading CLI context; `WorkflowDraftAuthoringApi.add_step_from_capability` owns declared capability outcomes and should produce route repair guidance; `bind_draft` behavior is already implemented and only needs a re-invocation regression.
**Tech Stack:** Python 3.14, Typer, pytest, existing `wf_api` semantic draft authoring and `wf_cli` command adapters.
---
## Scope
This plan implements three challenge-derived follow-ups:
1. `wf draft set-input --map input.text=local.text` fails immediately with a compact correction: local targets are bare names, so use `input.text=text`.
2. `wf draft add-step --route error=fail` for a capability that only declares `ok` reports declared, missing, and unknown outcomes plus an explicit remove/add repair.
3. Repeating a successful `wf draft bind input.x -> local.x` with the next revision remains valid and preserves one input binding.
This plan does **not** implement composite data shaping such as `state.title -> local.report.title` or synthesize a `state.report` object.
## File Map
- Modify `src/wf_cli/commands/drafts.py`
- Add `_parse_step_input_map_flags` beside `_parse_map_flags`.
- Use it only in `set_step_input_map`.
- Modify `tests/wf_cli/test_app.py`
- Add a CLI parse-level regression for `input.text=local.text`.
- Extend `set-input --help` assertions with a bare-target example.
- Modify `src/wf_api/draft_authoring.py`
- Extract or inline a small route mismatch message builder.
- Include actionable repair text for missing/unknown outcomes.
- Modify `tests/wf_api/test_drafts_service.py`
- Add single-outcome unknown-route repair coverage.
- Add repeated-bind coverage.
- Modify `docs/wf_cli.md`
- Modify `skills/wf-cli/SKILL.md`
- Modify `skills/wf-workflow/references/draft-workspaces.md`
- Modify `docs/current_roadmap.md`
- Mark these three roadmap bullets complete after implementation.
---
### Task 1: Reject `local.x` Targets In `set-input`
**Files:**
- Modify: `tests/wf_cli/test_app.py`
- Modify: `src/wf_cli/commands/drafts.py`
- [ ] **Step 1: Write the failing CLI regression test**
Add near `test_wf_draft_map_help_explains_replace_merge_and_validate`:
```python
def test_wf_draft_set_input_rejects_local_prefixed_target() -> None:
result = runner.invoke(
app,
[
"draft",
"set-input",
"report_ws",
"--revision",
"1",
"--step",
"render",
"--map",
"input.text=local.text",
],
)
assert result.exit_code == 2
output = " ".join(result.output.split())
assert "bare local field" in output
assert "input.text=text" in output
assert "input.text=local.text" in output
```
This test must fail before implementation because the command currently proceeds to context loading instead of rejecting the map shape.
- [ ] **Step 2: Run the test to verify red state**
Run:
```powershell
uv run pytest tests/wf_cli/test_app.py::test_wf_draft_set_input_rejects_local_prefixed_target -q
```
Expected before implementation: failure because the compact bare-target diagnostic is absent.
- [ ] **Step 3: Add a command-specific parser**
In `src/wf_cli/commands/drafts.py`, add after `_parse_map_flags`:
```python
def _parse_step_input_map_flags(values: list[str] | None) -> dict[str, str]:
"""Parse graph-source to bare-local input mappings for one draft step."""
parsed = _parse_map_flags(values)
for source, target in parsed.items():
if target.startswith("local."):
bare_target = target.removeprefix("local.")
raise typer.BadParameter(
"--map target must be a bare local field; "
f"use {source}={bare_target}, not {source}={target}"
)
return parsed
```
This deliberately rejects only the common unquoted `local.` prefix mistake. Do not reject quoted TOML local field names that merely contain punctuation.
- [ ] **Step 4: Route `set-input` through the new parser**
In `set_step_input_map`, replace:
```python
input_map = _parse_map_flags(mapping)
```
with:
```python
input_map = _parse_step_input_map_flags(mapping)
```
Do not change `set-output` or `set-workflow-output`; their target grammars differ.
- [ ] **Step 5: Improve `set-input` help text**
Extend the `set_step_input_map` docstring with:
```text
Targets are bare node-local field names. Use `--map input.text=text`, not
`--map input.text=local.text`.
```
In `test_wf_draft_map_help_explains_replace_merge_and_validate`, add:
```python
assert "input.text=text" in input_help
assert "input.text=local.text" in input_help
```
- [ ] **Step 6: Run CLI tests**
Run:
```powershell
uv run pytest tests/wf_cli/test_app.py -q -k "set_input_rejects_local_prefixed_target or map_help_explains_replace_merge_and_validate"
```
Expected: pass.
- [ ] **Step 7: Commit**
```powershell
git add src/wf_cli/commands/drafts.py tests/wf_cli/test_app.py
git commit -m "fix: explain draft set-input local targets"
```
---
### Task 2: Add Actionable Route Outcome Repairs
**Files:**
- Modify: `tests/wf_api/test_drafts_service.py`
- Modify: `src/wf_api/draft_authoring.py`
- [ ] **Step 1: Add the single-outcome regression test**
Add near `test_add_step_from_capability_rejects_unknown_routes_for_multi_outcome`:
```python
@pytest.mark.asyncio
async def test_add_step_rejects_unknown_single_outcome_route_with_repair(
tmp_path: Path,
) -> None:
artifact_store = FileWorkflowArtifactStore(
tmp_path / "drafts_unknown_single_outcome"
)
api, _service, authoring = _draft_api(artifact_store, register_echo=True)
await api.create_draft_workspace(
workspace_id="unknown_single",
draft=_echo_draft(),
)
with pytest.raises(ValueError) as exc_info:
await authoring.add_step_from_capability(
workspace_id="unknown_single",
revision=1,
step_id="second",
capability_name="demo.personal.echo_tool",
routes={"ok": "__end__", "error": "fail"},
)
message = str(exc_info.value)
assert "declared outcomes ('ok',)" in message
assert "unknown routes ['error']" in message
assert "remove --route entries for ['error']" in message
```
- [ ] **Step 2: Strengthen the existing multi-outcome test**
Change the existing context manager to capture the exception and assert:
```python
message = str(exc_info.value)
assert "declared outcomes ('ok', 'skipped')" in message
assert "unknown routes ['typo']" in message
assert "remove --route entries for ['typo']" in message
```
- [ ] **Step 3: Run tests to verify red state**
Run:
```powershell
uv run pytest tests/wf_api/test_drafts_service.py -q -k "unknown_single_outcome_route_with_repair or rejects_unknown_routes_for_multi_outcome"
```
Expected before implementation: tests fail because the message has no compact repair phrase.
- [ ] **Step 4: Build route mismatch and repair fragments**
In `WorkflowDraftAuthoringApi.add_step_from_capability`, replace the current `details` list and error construction with:
```python
details = [
f"declared_outcomes={declared_outcomes!r}",
f"missing_outcomes={sorted(missing_outcomes)!r}",
f"unknown_outcomes={sorted(unknown_outcomes)!r}",
]
repairs: list[str] = []
if unknown_outcomes:
repairs.append(
f"remove --route entries for {sorted(unknown_outcomes)!r}"
)
if missing_outcomes:
repairs.append(
f"add --route OUTCOME=TARGET for {sorted(missing_outcomes)!r}"
)
raise ValueError(
f"capability {capability_name!r} declares outcomes "
f"{declared_outcomes}, but routes has missing routes "
f"{sorted(missing_outcomes)} and unknown routes "
f"{sorted(unknown_outcomes)}; "
+ ", ".join(details)
+ "; repair: "
+ "; ".join(repairs)
)
```
Keep this in the semantic authoring layer because only it knows the live capability outcomes.
- [ ] **Step 5: Run focused route tests**
Run:
```powershell
uv run pytest tests/wf_api/test_drafts_service.py -q -k "add_step_from_capability and route"
```
Expected: all selected tests pass.
- [ ] **Step 6: Commit**
```powershell
git add src/wf_api/draft_authoring.py tests/wf_api/test_drafts_service.py
git commit -m "fix: add draft route repair guidance"
```
---
### Task 3: Pin Repeated Bind Behavior
**Files:**
- Modify: `tests/wf_api/test_drafts_service.py`
- [ ] **Step 1: Add the re-invocation regression**
Add after `test_bind_draft_workflow_input_to_step_input_reuses_existing_schema`:
```python
@pytest.mark.asyncio
async def test_bind_draft_workflow_input_to_step_input_can_repeat(
tmp_path: Path,
) -> None:
artifact_store = FileWorkflowArtifactStore(tmp_path / "drafts_bind_repeat")
api, _service, authoring = _draft_api(artifact_store, register_echo=True)
await api.create_draft_workspace(workspace_id="bind_ws", draft=_echo_draft())
first = await authoring.bind_draft(
workspace_id="bind_ws",
revision=1,
step_id="echo",
source_path="input.text",
target_path="local.text",
)
second = await authoring.bind_draft(
workspace_id="bind_ws",
revision=first["revision"],
step_id="echo",
source_path="input.text",
target_path="local.text",
)
workspace = await api.get_draft_workspace(
workspace_id="bind_ws", include_draft=True
)
assert first["status"] == "valid"
assert second["status"] == "valid"
assert second["revision"] == 3
assert workspace["draft"]["steps"]["echo"]["input"] == [
{"target": "text", "path": "input.text"}
]
```
This test documents current revision semantics: repeated semantic edits are safe but still consume a revision. Changing no-op revision behavior is out of scope.
- [ ] **Step 2: Run the regression**
Run:
```powershell
uv run pytest tests/wf_api/test_drafts_service.py::test_bind_draft_workflow_input_to_step_input_can_repeat -q
```
Expected: pass without production changes.
- [ ] **Step 3: Commit**
```powershell
git add tests/wf_api/test_drafts_service.py
git commit -m "test: pin repeated draft bind behavior"
```
---
### Task 4: Update Live Docs And Skills
**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`
- [ ] **Step 1: Update `docs/wf_cli.md`**
Add these points in the focused draft editing sections:
```markdown
`set-input` targets are bare node-local field names: write
`--map input.text=text`, not `--map input.text=local.text`.
When `add-step --route` rejects an outcome, use the declared outcomes and
repair text from the error. Remove unknown route entries and add one route for
each missing declared outcome.
```
- [ ] **Step 2: Update `skills/wf-cli/SKILL.md`**
Add concise agent rules:
```markdown
- `set-input --map` is `GRAPH_SOURCE=BARE_LOCAL_FIELD`; never prefix the target
with `local.`.
- For `add-step --route`, route only outcomes reported by `wf cap inspect` or
the command error's `declared_outcomes` field.
```
- [ ] **Step 3: Update draft workspace reference**
In `skills/wf-workflow/references/draft-workspaces.md`, add the same map grammar and route repair guidance beside the existing `set-input` and `add-step` examples.
- [ ] **Step 4: Mark roadmap bullets complete**
In `docs/current_roadmap.md`, move these three items from the planned follow-up list into completed wording:
```markdown
- Completed: `wf draft set-input` rejects `local.x` targets before RPC and
shows the equivalent bare-target mapping.
- Completed: `wf draft add-step --route` errors include declared outcomes and
direct add/remove repair guidance.
- Completed: repeated idempotent `wf draft bind input/state -> local` behavior
is covered by regression tests.
```
Leave the composite-binding/data-shaping item planned.
- [ ] **Step 5: Commit docs**
```powershell
git add docs/wf_cli.md skills/wf-cli/SKILL.md skills/wf-workflow/references/draft-workspaces.md docs/current_roadmap.md
git commit -m "docs: record draft CLI diagnostic guidance"
```
---
### Task 5: Verification And Archive
**Files:**
- Move: `docs/superpowers/plans/2026-06-29-draft-cli-diagnostics-polish.md`
- To: `docs/historical/superpowers/plans/2026-06-29-draft-cli-diagnostics-polish.md`
- [ ] **Step 1: Run focused tests**
```powershell
uv run pytest tests/wf_api/test_drafts_service.py tests/wf_cli/test_app.py -q -k "bind_draft or add_step or set_input or map_help"
```
Expected: all selected tests pass.
- [ ] **Step 2: Run static checks**
```powershell
uv run ruff check src/wf_api/draft_authoring.py src/wf_cli/commands/drafts.py tests/wf_api/test_drafts_service.py tests/wf_cli/test_app.py
uv run ruff format --check src/wf_api/draft_authoring.py src/wf_cli/commands/drafts.py tests/wf_api/test_drafts_service.py tests/wf_cli/test_app.py
uv run basedpyright --level error src/wf_api/draft_authoring.py src/wf_cli/commands/drafts.py tests/wf_api/test_drafts_service.py tests/wf_cli/test_app.py
```
Expected: no lint, formatting, or type errors.
- [ ] **Step 3: Run live smoke when the RPC server is available**
```powershell
uv run wf draft set-input smoke --revision 1 --step call --map input.text=local.text
```
Expected: exit code 2 with a compact correction containing `input.text=text`, before any RPC workspace lookup.
For route repair, use a disposable draft and a known single-outcome capability:
```powershell
$id = 'smoke_route_' + (Get-Date -Format 'HHmmss')
uv run wf draft create $id --capability wf.std.constant --name $id
uv run wf draft add-step $id --revision 1 --step second --capability wf.std.constant --route ok=__end__ --route error=fail
```
Expected: the error lists declared `ok`, identifies unknown `error`, and says to remove the unknown route entry.
- [ ] **Step 4: Archive the plan**
```powershell
git mv docs/superpowers/plans/2026-06-29-draft-cli-diagnostics-polish.md docs/historical/superpowers/plans/2026-06-29-draft-cli-diagnostics-polish.md
git add docs/historical/superpowers/plans/2026-06-29-draft-cli-diagnostics-polish.md
git commit -m "docs: archive draft CLI diagnostics plan"
```
---
## Self-Review
- Spec coverage: all three small roadmap follow-ups map to explicit tests and implementation tasks.
- Scope boundary: composite object/data-shaping semantics remain separate.
- Placeholder scan: no `TBD`, `TODO`, or unspecified implementation steps remain.
- Type consistency: the plan uses current symbols and command names: `_parse_map_flags`, `set_step_input_map`, `WorkflowDraftAuthoringApi.add_step_from_capability`, `bind_draft`, `wf draft set-input`, and `wf draft add-step`.