Files
lda-wf/docs/historical/superpowers/plans/2026-05-20-wrapper-authoring-hints.md
T

822 lines
26 KiB
Markdown

# Wrapper Authoring Hints 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 enum-backed wrapper authoring hints to workflow capability inspection so LLM and human MCP clients can scaffold wrapper drafts without guessing raw workflow plans.
**Architecture:** Keep MCP request/response names as plain JSON strings, but compute wrapper hints in a pure workflow-surface helper. The helper uses typed Pydantic models with enum fields for confidence, outcome policy, candidate kinds, and missing-decision kinds, then `inspect_capability` attaches the hint payload to live NodeSpec and saved wrapper details.
**Tech Stack:** Python 3.14, Pydantic v2, FastMCP-facing dict payloads, pytest, ruff, basedpyright.
---
## File Structure
- Create `src/wf_mcp/workflow_surface/wrapper_hints.py`
- Owns enum-backed hint models.
- Owns pure functions that derive hints from one capability contract.
- Must not call MCP, stores, workflow runtime, or FastMCP.
- Modify `src/wf_mcp/workflow_surface/handlers.py`
- Imports the pure hint helper.
- Adds `wrapper_hints` to `inspect_capability` payloads.
- Modify `src/wf_mcp/workflow_surface/models.py`
- Adds JSON-schema-visible response models if MCP tool schemas need stronger documentation.
- Do this only after the helper shape stabilizes.
- Test `tests/wf_mcp/test_workflow_wrapper_hints.py`
- Focused unit tests for hint derivation.
- Test `tests/wf_mcp/test_workflow_surface.py`
- Integration tests proving `inspect_capability` includes hints.
- Docs `docs/workflow_capabilities.md`
- Explain that hints are scaffolding, not semantic guarantees.
## Vocabulary
Use enums for all classification/type fields. Do not return magic strings for these fields from helper internals.
```python
from enum import StrEnum
class WrapperHintConfidence(StrEnum):
HIGH = "high"
MEDIUM = "medium"
LOW = "low"
class WrapperOutcomePolicy(StrEnum):
PRESERVE_DECLARED = "preserve_declared"
MANUAL_MAPPING_REQUIRED = "manual_mapping_required"
class OutcomeCandidateKind(StrEnum):
BOOLEAN_CONTROL_FIELD = "boolean_control_field"
class MissingDecisionKind(StrEnum):
CHOOSE_OUTPUT_FIELDS = "choose_output_fields"
REVIEW_NESTED_OUTPUT = "review_nested_output"
CONFIRM_BOOLEAN_OUTCOMES = "confirm_boolean_outcomes"
CHOOSE_ERROR_MAPPING = "choose_error_mapping"
```
MCP JSON still serializes those enum values as strings.
## Task 1: Add Enum-Backed Hint Models
**Files:**
- Create: `src/wf_mcp/workflow_surface/wrapper_hints.py`
- Test: `tests/wf_mcp/test_workflow_wrapper_hints.py`
- [ ] **Step 1: Write failing model serialization test**
Create `tests/wf_mcp/test_workflow_wrapper_hints.py`:
```python
from __future__ import annotations
from wf_mcp.workflow_surface.wrapper_hints import (
MissingDecision,
MissingDecisionKind,
OutcomeCandidate,
OutcomeCandidateKind,
WrapperAuthoringHints,
WrapperHintConfidence,
WrapperOutcomePolicy,
)
def test_wrapper_hint_models_serialize_enum_fields_as_strings() -> None:
hints = WrapperAuthoringHints(
capability_name="demo.personal.echo_tool",
confidence=WrapperHintConfidence.MEDIUM,
declared_outcomes=["ok", "error"],
suggested_wrapper_outcomes=["ok", "error"],
outcome_policy=WrapperOutcomePolicy.PRESERVE_DECLARED,
input_schema={"type": "object", "properties": {}},
state_schema={"type": "object", "properties": {}},
output_schema={"type": "object", "properties": {}},
input_map={},
output_map={},
outcome_candidates=[
OutcomeCandidate(
kind=OutcomeCandidateKind.BOOLEAN_CONTROL_FIELD,
source="output.success",
candidate_outcomes=["success", "failure"],
confidence=WrapperHintConfidence.MEDIUM,
reason="top-level boolean field with control-like name",
automatic=False,
)
],
missing_decisions=[
MissingDecision(
kind=MissingDecisionKind.CONFIRM_BOOLEAN_OUTCOMES,
message="Confirm whether output.success should control routing.",
)
],
notes=["Hints are scaffolding, not semantic guarantees."],
)
dumped = hints.model_dump(mode="json")
assert dumped["confidence"] == "medium"
assert dumped["outcome_policy"] == "preserve_declared"
assert dumped["outcome_candidates"][0]["kind"] == "boolean_control_field"
assert dumped["missing_decisions"][0]["kind"] == "confirm_boolean_outcomes"
```
- [ ] **Step 2: Run model test to verify it fails**
Run:
```bash
uv run --with pytest pytest tests/wf_mcp/test_workflow_wrapper_hints.py::test_wrapper_hint_models_serialize_enum_fields_as_strings -q
```
Expected: import failure because `wf_mcp.workflow_surface.wrapper_hints` does not exist.
- [ ] **Step 3: Implement model definitions**
Create `src/wf_mcp/workflow_surface/wrapper_hints.py`:
```python
from __future__ import annotations
from enum import StrEnum
from typing import Any
from pydantic import BaseModel, Field
JsonObject = dict[str, Any]
class WrapperHintConfidence(StrEnum):
"""Coarse confidence for generated wrapper scaffolding hints."""
HIGH = "high"
MEDIUM = "medium"
LOW = "low"
class WrapperOutcomePolicy(StrEnum):
"""How wrapper outcomes were chosen."""
PRESERVE_DECLARED = "preserve_declared"
MANUAL_MAPPING_REQUIRED = "manual_mapping_required"
class OutcomeCandidateKind(StrEnum):
"""Reason a field was offered as a possible outcome source."""
BOOLEAN_CONTROL_FIELD = "boolean_control_field"
class MissingDecisionKind(StrEnum):
"""Typed action item a human or LLM must decide before saving a wrapper."""
CHOOSE_OUTPUT_FIELDS = "choose_output_fields"
REVIEW_NESTED_OUTPUT = "review_nested_output"
CONFIRM_BOOLEAN_OUTCOMES = "confirm_boolean_outcomes"
CHOOSE_ERROR_MAPPING = "choose_error_mapping"
class OutcomeCandidate(BaseModel):
"""One possible outcome mapping that must not be applied automatically."""
kind: OutcomeCandidateKind
source: str = Field(description="Output path such as output.success.")
candidate_outcomes: list[str]
confidence: WrapperHintConfidence
reason: str
automatic: bool = False
class MissingDecision(BaseModel):
"""One explicit decision required before a wrapper should be saved."""
kind: MissingDecisionKind
message: str
class WrapperAuthoringHints(BaseModel):
"""Scaffold for creating a workflow wrapper around one capability."""
capability_name: str
confidence: WrapperHintConfidence
declared_outcomes: list[str]
suggested_wrapper_outcomes: list[str]
outcome_policy: WrapperOutcomePolicy
input_schema: JsonObject
state_schema: JsonObject
output_schema: JsonObject
input_map: dict[str, str]
output_map: dict[str, str]
outcome_candidates: list[OutcomeCandidate] = Field(default_factory=list)
missing_decisions: list[MissingDecision] = Field(default_factory=list)
notes: list[str] = Field(default_factory=list)
```
- [ ] **Step 4: Run model test to verify it passes**
Run:
```bash
uv run --with pytest pytest tests/wf_mcp/test_workflow_wrapper_hints.py::test_wrapper_hint_models_serialize_enum_fields_as_strings -q
```
Expected: pass.
## Task 2: Derive Simple Wrapper Hints From Capability Schemas
**Files:**
- Modify: `src/wf_mcp/workflow_surface/wrapper_hints.py`
- Test: `tests/wf_mcp/test_workflow_wrapper_hints.py`
- [ ] **Step 1: Add failing simple-schema hint test**
Append to `tests/wf_mcp/test_workflow_wrapper_hints.py`:
```python
from wf_mcp.workflow_surface.wrapper_hints import wrapper_hints_for_capability
def test_wrapper_hints_scaffold_simple_object_input_and_output() -> None:
hints = wrapper_hints_for_capability(
capability_name="demo.personal.echo_tool",
input_schema={
"type": "object",
"properties": {"text": {"type": "string"}},
"required": ["text"],
},
output_schema={
"type": "object",
"properties": {"echoed": {"type": "string"}},
"required": ["echoed"],
},
outcomes=["ok"],
)
dumped = hints.model_dump(mode="json")
assert dumped["confidence"] == "high"
assert dumped["declared_outcomes"] == ["ok"]
assert dumped["suggested_wrapper_outcomes"] == ["ok"]
assert dumped["outcome_policy"] == "preserve_declared"
assert dumped["input_map"] == {"input.text": "text"}
assert dumped["output_map"] == {"echoed": "state.echoed"}
assert dumped["state_schema"]["properties"]["echoed"]["type"] == "string"
assert dumped["output_schema"]["properties"]["echoed"]["type"] == "string"
assert dumped["missing_decisions"] == []
```
- [ ] **Step 2: Run simple-schema test to verify it fails**
Run:
```bash
uv run --with pytest pytest tests/wf_mcp/test_workflow_wrapper_hints.py::test_wrapper_hints_scaffold_simple_object_input_and_output -q
```
Expected: import failure or attribute error for `wrapper_hints_for_capability`.
- [ ] **Step 3: Implement `wrapper_hints_for_capability`**
Add to `src/wf_mcp/workflow_surface/wrapper_hints.py`:
```python
CONTROL_BOOLEAN_NAMES = {
"success",
"ok",
"failed",
"error",
"is_error",
"needs_input",
"requires_approval",
"approved",
"rejected",
"has_more",
"done",
"complete",
}
def wrapper_hints_for_capability(
*,
capability_name: str,
input_schema: JsonObject,
output_schema: JsonObject,
outcomes: list[str] | tuple[str, ...],
) -> WrapperAuthoringHints:
"""Derive conservative wrapper scaffolding for one workflow capability."""
input_properties = _object_properties(input_schema)
output_properties = _object_properties(output_schema)
input_map = {f"input.{name}": name for name in sorted(input_properties)}
output_map = {name: f"state.{name}" for name in sorted(output_properties)}
state_schema = {
"type": "object",
"properties": {
name: schema for name, schema in sorted(output_properties.items())
},
}
wrapper_output_schema = {
"type": "object",
"properties": {
name: schema for name, schema in sorted(output_properties.items())
},
}
missing_decisions = _missing_decisions_for_output(output_schema)
outcome_candidates = _boolean_outcome_candidates(output_properties)
if outcome_candidates:
missing_decisions.append(
MissingDecision(
kind=MissingDecisionKind.CONFIRM_BOOLEAN_OUTCOMES,
message="Confirm whether boolean output fields should control wrapper routing.",
)
)
confidence = _confidence_for_hint(
input_schema=input_schema,
output_schema=output_schema,
missing_decisions=missing_decisions,
outcome_candidates=outcome_candidates,
)
return WrapperAuthoringHints(
capability_name=capability_name,
confidence=confidence,
declared_outcomes=list(outcomes),
suggested_wrapper_outcomes=list(outcomes),
outcome_policy=WrapperOutcomePolicy.PRESERVE_DECLARED,
input_schema=input_schema,
state_schema=state_schema,
output_schema=wrapper_output_schema,
input_map=input_map,
output_map=output_map,
outcome_candidates=outcome_candidates,
missing_decisions=missing_decisions,
notes=[
"Hints are scaffolding, not semantic guarantees.",
"Declared outcomes are preserved; output-field outcome inference is not automatic.",
],
)
def _object_properties(schema: JsonObject) -> dict[str, JsonObject]:
properties = schema.get("properties")
if not isinstance(properties, dict):
return {}
return {
str(name): value
for name, value in properties.items()
if isinstance(value, dict)
}
```
- [ ] **Step 4: Implement confidence and missing decision helpers**
Add below `_object_properties` in `src/wf_mcp/workflow_surface/wrapper_hints.py`:
```python
def _missing_decisions_for_output(output_schema: JsonObject) -> list[MissingDecision]:
properties = _object_properties(output_schema)
if not properties:
return [
MissingDecision(
kind=MissingDecisionKind.CHOOSE_OUTPUT_FIELDS,
message="Capability output schema has no top-level object properties to map.",
)
]
decisions: list[MissingDecision] = []
for name, schema in sorted(properties.items()):
schema_type = schema.get("type")
if schema_type == "object" or schema_type == "array":
decisions.append(
MissingDecision(
kind=MissingDecisionKind.REVIEW_NESTED_OUTPUT,
message=f"Review output.{name}; nested or collection outputs may need explicit mapping.",
)
)
return decisions
def _boolean_outcome_candidates(
output_properties: dict[str, JsonObject],
) -> list[OutcomeCandidate]:
candidates: list[OutcomeCandidate] = []
for name, schema in sorted(output_properties.items()):
if schema.get("type") != "boolean":
continue
if name.casefold() not in CONTROL_BOOLEAN_NAMES:
continue
candidates.append(
OutcomeCandidate(
kind=OutcomeCandidateKind.BOOLEAN_CONTROL_FIELD,
source=f"output.{name}",
candidate_outcomes=_candidate_outcomes_for_boolean_name(name),
confidence=WrapperHintConfidence.MEDIUM,
reason="top-level boolean field with control-like name",
automatic=False,
)
)
return candidates
def _candidate_outcomes_for_boolean_name(name: str) -> list[str]:
normalized = name.casefold()
if normalized in {"success", "ok", "done", "complete"}:
return ["success", "failure"]
if normalized in {"failed", "error", "is_error"}:
return ["error", "ok"]
if normalized in {"approved", "rejected"}:
return ["approved", "rejected"]
if normalized in {"needs_input", "requires_approval"}:
return [normalized, "done"]
if normalized == "has_more":
return ["has_more", "done"]
return ["true", "false"]
def _confidence_for_hint(
*,
input_schema: JsonObject,
output_schema: JsonObject,
missing_decisions: list[MissingDecision],
outcome_candidates: list[OutcomeCandidate],
) -> WrapperHintConfidence:
if not _object_properties(input_schema) or not _object_properties(output_schema):
return WrapperHintConfidence.LOW
if any(
decision.kind == MissingDecisionKind.REVIEW_NESTED_OUTPUT
for decision in missing_decisions
):
return WrapperHintConfidence.LOW
if missing_decisions or outcome_candidates:
return WrapperHintConfidence.MEDIUM
return WrapperHintConfidence.HIGH
```
- [ ] **Step 5: Run wrapper hint tests**
Run:
```bash
uv run --with pytest pytest tests/wf_mcp/test_workflow_wrapper_hints.py -q
```
Expected: pass.
## Task 3: Add Boolean Outcome Candidate Tests
**Files:**
- Modify: `tests/wf_mcp/test_workflow_wrapper_hints.py`
- Modify: `src/wf_mcp/workflow_surface/wrapper_hints.py` only if tests reveal gaps.
- [ ] **Step 1: Add candidate and non-candidate tests**
Append:
```python
def test_wrapper_hints_offer_boolean_outcome_candidates_without_auto_mapping() -> None:
hints = wrapper_hints_for_capability(
capability_name="demo.personal.submit",
input_schema={"type": "object", "properties": {"text": {"type": "string"}}},
output_schema={
"type": "object",
"properties": {
"success": {"type": "boolean"},
"message": {"type": "string"},
},
},
outcomes=["ok"],
)
dumped = hints.model_dump(mode="json")
candidate = dumped["outcome_candidates"][0]
assert dumped["confidence"] == "medium"
assert candidate["kind"] == "boolean_control_field"
assert candidate["source"] == "output.success"
assert candidate["candidate_outcomes"] == ["success", "failure"]
assert candidate["automatic"] is False
assert dumped["outcome_policy"] == "preserve_declared"
assert dumped["suggested_wrapper_outcomes"] == ["ok"]
assert dumped["missing_decisions"][0]["kind"] == "confirm_boolean_outcomes"
def test_wrapper_hints_do_not_treat_arbitrary_booleans_as_outcomes() -> None:
hints = wrapper_hints_for_capability(
capability_name="demo.personal.profile",
input_schema={"type": "object", "properties": {"user_id": {"type": "string"}}},
output_schema={
"type": "object",
"properties": {
"is_admin": {"type": "boolean"},
"name": {"type": "string"},
},
},
outcomes=["ok"],
)
dumped = hints.model_dump(mode="json")
assert dumped["confidence"] == "high"
assert dumped["outcome_candidates"] == []
assert dumped["missing_decisions"] == []
```
- [ ] **Step 2: Run tests**
Run:
```bash
uv run --with pytest pytest tests/wf_mcp/test_workflow_wrapper_hints.py -q
```
Expected: pass. If arbitrary boolean fields produce candidates, fix `CONTROL_BOOLEAN_NAMES` filtering rather than deleting the test.
## Task 4: Add Complex Output Missing Decision Tests
**Files:**
- Modify: `tests/wf_mcp/test_workflow_wrapper_hints.py`
- Modify: `src/wf_mcp/workflow_surface/wrapper_hints.py` only if tests reveal gaps.
- [ ] **Step 1: Add nested-output and empty-output tests**
Append:
```python
def test_wrapper_hints_mark_nested_outputs_as_low_confidence() -> None:
hints = wrapper_hints_for_capability(
capability_name="demo.personal.search",
input_schema={"type": "object", "properties": {"query": {"type": "string"}}},
output_schema={
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"type": "object",
"properties": {"title": {"type": "string"}},
},
}
},
},
outcomes=["ok"],
)
dumped = hints.model_dump(mode="json")
assert dumped["confidence"] == "low"
assert dumped["missing_decisions"][0]["kind"] == "review_nested_output"
assert dumped["output_map"] == {"results": "state.results"}
def test_wrapper_hints_mark_empty_output_schema_as_low_confidence() -> None:
hints = wrapper_hints_for_capability(
capability_name="demo.personal.no_output",
input_schema={"type": "object", "properties": {"text": {"type": "string"}}},
output_schema={"type": "object", "properties": {}},
outcomes=["ok"],
)
dumped = hints.model_dump(mode="json")
assert dumped["confidence"] == "low"
assert dumped["input_map"] == {"input.text": "text"}
assert dumped["output_map"] == {}
assert dumped["missing_decisions"][0]["kind"] == "choose_output_fields"
```
- [ ] **Step 2: Run tests**
Run:
```bash
uv run --with pytest pytest tests/wf_mcp/test_workflow_wrapper_hints.py -q
```
Expected: pass.
## Task 5: Wire Hints Into `inspect_capability`
**Files:**
- Modify: `src/wf_mcp/workflow_surface/handlers.py`
- Test: `tests/wf_mcp/test_workflow_surface.py`
- [ ] **Step 1: Add failing live capability integration test**
In `tests/wf_mcp/test_workflow_surface.py`, after `test_workflow_surface_inspects_one_capability`, add:
```python
def test_workflow_surface_inspect_capability_includes_wrapper_hints() -> None:
service = WfMcpService(
store=FileStore(local_temp_root() / "surface_wrapper_hints_mcp"),
artifact_store=FileWorkflowArtifactStore(
local_temp_root() / "surface_wrapper_hints_artifacts"
),
)
service.register_connection(
ConnectionConfig(id="demo.personal", server="demo", account="personal")
)
service.register_specs("demo.personal", echo_tool)
handlers = WorkflowSurfaceHandlers(service)
payload = asyncio.run(
handlers.inspect_capability(qualified_name="demo.personal.echo_tool")
)
hints = payload["wrapper_hints"]
assert hints["capability_name"] == "demo.personal.echo_tool"
assert hints["declared_outcomes"] == ["ok"]
assert hints["input_map"] == {"input.text": "text"}
assert hints["output_map"] == {"echoed": "state.echoed"}
assert hints["outcome_policy"] == "preserve_declared"
```
- [ ] **Step 2: Run integration test to verify it fails**
Run:
```bash
uv run --with pytest pytest tests/wf_mcp/test_workflow_surface.py::test_workflow_surface_inspect_capability_includes_wrapper_hints -q
```
Expected: failure because `wrapper_hints` is absent.
- [ ] **Step 3: Attach hints for live NodeSpecs**
In `src/wf_mcp/workflow_surface/handlers.py`, import:
```python
from .wrapper_hints import wrapper_hints_for_capability
```
Then in `inspect_capability`, replace the live-detail return with:
```python
detail_payload = detail.model_dump(mode="json")
detail_payload["wrapper_hints"] = wrapper_hints_for_capability(
capability_name=detail.name,
input_schema=detail.input_schema,
output_schema=detail.output_schema,
outcomes=detail.outcomes,
).model_dump(mode="json")
return detail_payload
```
- [ ] **Step 4: Run integration test**
Run:
```bash
uv run --with pytest pytest tests/wf_mcp/test_workflow_surface.py::test_workflow_surface_inspect_capability_includes_wrapper_hints -q
```
Expected: pass.
## Task 6: Add Hints For Saved Wrapper Artifact Inspection
**Files:**
- Modify: `src/wf_mcp/workflow_surface/handlers.py`
- Test: `tests/wf_mcp/test_workflow_surface.py`
- [ ] **Step 1: Add failing saved-wrapper hint assertion**
In existing `test_workflow_surface_inspects_saved_wrapper_capability`, add:
```python
hints = payload["wrapper_hints"]
assert hints["capability_name"] == "workflow.echo_wrapper.v1"
assert hints["declared_outcomes"] == ["completed"]
assert hints["suggested_wrapper_outcomes"] == ["completed"]
assert hints["input_map"] == {"input.text": "text"}
assert hints["output_map"] == {"echoed": "state.echoed"}
```
- [ ] **Step 2: Run saved-wrapper test to verify it fails**
Run:
```bash
uv run --with pytest pytest tests/wf_mcp/test_workflow_surface.py::test_workflow_surface_inspects_saved_wrapper_capability -q
```
Expected: failure because wrapper detail lacks `wrapper_hints`.
- [ ] **Step 3: Attach hints for wrapper artifacts**
In `_wrapper_capability_detail`, add `wrapper_hints` to the returned dict:
```python
"wrapper_hints": wrapper_hints_for_capability(
capability_name=_artifact_capability_id(artifact),
input_schema=artifact.input_schema,
output_schema=artifact.output_schema,
outcomes=list(artifact.outcomes),
).model_dump(mode="json"),
```
- [ ] **Step 4: Run saved-wrapper test**
Run:
```bash
uv run --with pytest pytest tests/wf_mcp/test_workflow_surface.py::test_workflow_surface_inspects_saved_wrapper_capability -q
```
Expected: pass.
## Task 7: Document Hint Semantics
**Files:**
- Modify: `docs/workflow_capabilities.md`
- [ ] **Step 1: Add documentation section**
Add a section titled `## Wrapper Authoring Hints`:
```markdown
## Wrapper Authoring Hints
`wf.workflow.inspect_capability` returns `wrapper_hints` for planner-visible
capabilities. These hints are scaffolding for draft creation, not semantic
guarantees.
Declared capability outcomes are authoritative and are preserved by default.
Boolean output fields may appear as `outcome_candidates` when they have
control-like names such as `success`, `error`, `approved`, or `has_more`, but
they are never wired automatically. A wrapper author must explicitly confirm
whether those fields should become routing conditions.
`confidence` is intentionally coarse:
- `high`: simple object input/output schemas and no missing decisions.
- `medium`: usable scaffold with candidate decisions, such as boolean outcome
candidates.
- `low`: missing or nested output choices require explicit authoring.
`missing_decisions` is a typed list of decisions the author should resolve
before saving a wrapper. MCP clients should show these prominently rather than
treating the scaffold as complete.
```
- [ ] **Step 2: Run docs-adjacent tests**
Run:
```bash
uv run --with pytest pytest tests/wf_mcp/test_workflow_wrapper_hints.py tests/wf_mcp/test_workflow_surface.py -q
```
Expected: pass.
## Task 8: Final Verification
**Files:**
- All touched files.
- [ ] **Step 1: Run focused tests**
Run:
```bash
uv run --with pytest pytest tests/wf_mcp/test_workflow_wrapper_hints.py tests/wf_mcp/test_workflow_surface.py tests/wf_mcp/test_workflow_surface_refs.py -q
```
Expected: pass.
- [ ] **Step 2: Run full tests if the workspace is not mid-edit**
Run:
```bash
uv run --with pytest pytest -q
```
Expected: pass, allowing intentional environment-dependent skips. If the user is actively editing `tests/rewrite`, run focused tests only and state that full-suite verification was deferred.
- [ ] **Step 3: Run static checks**
Run:
```bash
uvx ruff check src/wf_mcp/workflow_surface tests/wf_mcp/test_workflow_wrapper_hints.py tests/wf_mcp/test_workflow_surface.py
uv run basedpyright --level error src/wf_mcp/workflow_surface tests/wf_mcp/test_workflow_wrapper_hints.py tests/wf_mcp/test_workflow_surface.py
uvx ruff format --check src/wf_mcp/workflow_surface tests/wf_mcp/test_workflow_wrapper_hints.py tests/wf_mcp/test_workflow_surface.py
```
Expected: ruff passes, basedpyright has 0 errors, and formatting is clean.
## Self-Review
- Spec coverage: The plan covers enum-backed type fields, conservative outcome suggestions, boolean candidates, confidence/missing-decision UX, inspect-capability integration, and docs.
- Placeholder scan: No `TBD`, `TODO`, or unspecified implementation steps remain.
- Type consistency: `WrapperHintConfidence`, `WrapperOutcomePolicy`, `OutcomeCandidateKind`, and `MissingDecisionKind` are defined before use and serialize through Pydantic models.
- Scope check: This plan does not create or save wrappers automatically; it only adds hint payloads for authoring.