---
phase: 18-architecture-cleanup
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
  - src/ztlctl/workspace_modes.py
  - src/ztlctl/services/export.py
  - src/ztlctl/mcp/generator.py
  - src/ztlctl/plugins/manager.py
  - src/ztlctl/services/result.py
  - tests/mcp/test_generator.py
  - .planning/REQUIREMENTS.md
autonomous: true
requirements: [ARCH-10, DEBT-05, DEBT-06]

must_haves:
  truths:
    - "workspace_modes.py is deleted and no imports reference it"
    - "No ActionDefinition uses the category string 'mutation' — custom note type update/close actions use 'lifecycle'"
    - "_DEFAULT_ACTIVE_CATEGORIES does not contain 'mutation'"
    - "ServiceError.recovery field is documented as actively used (controllers + MCP layer)"
  artifacts:
    - path: "src/ztlctl/services/export.py"
      provides: "Direct import from workspace_profiles instead of workspace_modes"
      contains: "from ztlctl.workspace_profiles import normalize_dashboard_viewer"
    - path: "src/ztlctl/mcp/generator.py"
      provides: "Clean category set without phantom mutation"
      contains: "creation"
    - path: "src/ztlctl/plugins/manager.py"
      provides: "Custom note type actions using lifecycle category"
      contains: "category=\"lifecycle\""
  key_links:
    - from: "src/ztlctl/services/export.py"
      to: "src/ztlctl/workspace_profiles.py"
      via: "direct import replacing workspace_modes indirection"
      pattern: "from ztlctl.workspace_profiles import"
    - from: "src/ztlctl/plugins/manager.py"
      to: "src/ztlctl/mcp/generator.py"
      via: "category alignment — both use lifecycle, not mutation"
      pattern: "lifecycle"
---

<objective>
Remove compatibility residue (deprecated workspace_modes.py wrapper), fix phantom mutation category, and resolve ServiceError.recovery debt.

Purpose: ARCH-10 removes dead indirection layers. DEBT-05 aligns category naming so plugin-contributed custom note type actions match core patterns. DEBT-06 is resolved by documenting that the recovery field IS actively used by controllers and the MCP response layer.
Output: Cleaner import graph, consistent category naming, updated REQUIREMENTS.md
</objective>

<execution_context>
@~/.claude/get-shit-done/workflows/execute-plan.md
@~/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
</context>

<tasks>

<task type="auto">
  <name>Task 1: Remove workspace_modes.py and fix mutation category</name>
  <files>src/ztlctl/workspace_modes.py, src/ztlctl/services/export.py, src/ztlctl/mcp/generator.py, src/ztlctl/plugins/manager.py, tests/mcp/test_generator.py</files>
  <read_first>
    src/ztlctl/workspace_modes.py
    src/ztlctl/services/export.py
    src/ztlctl/workspace_profiles.py
    src/ztlctl/mcp/generator.py
    src/ztlctl/plugins/manager.py
    tests/mcp/test_generator.py
  </read_first>
  <action>
    **ARCH-10 — Delete workspace_modes.py:**
    1. In `src/ztlctl/services/export.py` line 22, replace `from ztlctl.workspace_modes import normalize_viewer` with `from ztlctl.workspace_profiles import normalize_dashboard_viewer as normalize_viewer`. The function signature is identical: `normalize_dashboard_viewer(value: str) -> tuple[Literal["obsidian", "none"], str | None]`.
    2. Delete `src/ztlctl/workspace_modes.py` entirely.
    3. Delete `src/ztlctl/__pycache__/workspace_modes.cpython-313.pyc` if it exists.

    **DEBT-05 — Fix phantom mutation category:**
    1. In `src/ztlctl/plugins/manager.py`, change the two occurrences of `category="mutation"` (lines ~690 and ~701, in the custom note type update and close ActionDefinition registrations) to `category="lifecycle"`. This matches the core update/close actions in `actions/_lifecycle.py`.
    2. In `src/ztlctl/mcp/generator.py` line 87, remove `"mutation"` from the `_DEFAULT_ACTIVE_CATEGORIES` frozenset. The resulting set should be: `{"creation", "query", "graph", "lifecycle", "session"}`.
    3. In `tests/mcp/test_generator.py` line 280, remove `"mutation"` from the assertion list. The test should check for `("creation", "query", "graph", "lifecycle", "session")`.
  </action>
  <verify>
    <automated>cd /Users/shparki/Documents/Workspace/thatdev/ztlctl && uv run python -c "from ztlctl.services.export import ExportService; print('export import OK')" && uv run python -c "from ztlctl.mcp.generator import _DEFAULT_ACTIVE_CATEGORIES; assert 'mutation' not in _DEFAULT_ACTIVE_CATEGORIES; print('no mutation in defaults')" && uv run pytest tests/mcp/test_generator.py tests/services/test_export.py -x -q 2>&1 | tail -5</automated>
  </verify>
  <acceptance_criteria>
    - `src/ztlctl/workspace_modes.py` does not exist
    - `grep -r "workspace_modes" src/` returns no matches
    - `grep "mutation" src/ztlctl/mcp/generator.py` returns no matches
    - `grep 'category="mutation"' src/ztlctl/plugins/manager.py` returns no matches
    - `grep 'category="lifecycle"' src/ztlctl/plugins/manager.py` returns 2 matches (update + close handlers)
  </acceptance_criteria>
  <done>workspace_modes.py deleted, export.py uses direct import, mutation category removed from defaults and plugin manager uses lifecycle instead</done>
</task>

<task type="auto">
  <name>Task 2: Resolve DEBT-06 and update REQUIREMENTS.md</name>
  <files>src/ztlctl/services/result.py, .planning/REQUIREMENTS.md</files>
  <read_first>
    src/ztlctl/services/result.py
    src/ztlctl/controllers/base.py
    src/ztlctl/controllers/discovery.py
    src/ztlctl/mcp/response.py
    .planning/REQUIREMENTS.md
  </read_first>
  <action>
    **DEBT-06 — ServiceError.recovery field resolution:**
    The `recovery` field IS actively used in production code:
    - `controllers/base.py:113` — populates recovery on plugin rejection
    - `controllers/discovery.py:77,122,137` — populates recovery on category errors
    - `mcp/response.py:156` — reads recovery to build MCP error responses

    Resolution: Keep the field. Add a docstring to `ServiceError.recovery` in `src/ztlctl/services/result.py` clarifying its purpose. Change line 23 from:
    ```
    recovery: str | None = None
    ```
    to:
    ```
    recovery: str | None = None
    """Optional recovery hint for agents/MCP consumers. Populated by controllers."""
    ```
    Use a Pydantic Field with description instead: `recovery: str | None = Field(default=None, description="Recovery hint for agents/MCP consumers. Populated by controllers on validation or plugin-rejection errors.")`

    **Update REQUIREMENTS.md:**
    Mark ARCH-10, DEBT-05, DEBT-06 as complete:
    - Line 21: Change `- [ ] **ARCH-10**:` to `- [x] **ARCH-10**:`
    - Line 29: Change `- [ ] **DEBT-05**:` to `- [x] **DEBT-05**:`
    - Line 30: Change `- [ ] **DEBT-06**:` to `- [x] **DEBT-06**:`
    - In traceability table: update ARCH-10, DEBT-05, DEBT-06 status from `Pending` to `Complete`
  </action>
  <verify>
    <automated>cd /Users/shparki/Documents/Workspace/thatdev/ztlctl && uv run python -c "from ztlctl.services.result import ServiceError; e = ServiceError(code='X', message='Y', recovery='Z'); assert e.recovery == 'Z'; print('recovery field OK')" && uv run pytest tests/services/test_result.py -x -q 2>&1 | tail -3</automated>
  </verify>
  <acceptance_criteria>
    - `grep "description=.*Recovery hint" src/ztlctl/services/result.py` returns 1 match
    - `grep "\[x\] \*\*ARCH-10\*\*" .planning/REQUIREMENTS.md` returns 1 match
    - `grep "\[x\] \*\*DEBT-05\*\*" .planning/REQUIREMENTS.md` returns 1 match
    - `grep "\[x\] \*\*DEBT-06\*\*" .planning/REQUIREMENTS.md` returns 1 match
  </acceptance_criteria>
  <done>ServiceError.recovery documented with Field description, REQUIREMENTS.md updated for ARCH-10, DEBT-05, DEBT-06</done>
</task>

</tasks>

<verification>
- `uv run ruff check src/ztlctl/services/export.py src/ztlctl/mcp/generator.py src/ztlctl/plugins/manager.py src/ztlctl/services/result.py`
- `uv run mypy src/ztlctl/services/export.py src/ztlctl/mcp/generator.py src/ztlctl/plugins/manager.py src/ztlctl/services/result.py`
- `uv run pytest tests/mcp/test_generator.py tests/services/test_result.py tests/services/test_export.py -x`
- No import of `workspace_modes` anywhere in `src/`
</verification>

<success_criteria>
- workspace_modes.py deleted, export.py imports directly from workspace_profiles
- No "mutation" category in _DEFAULT_ACTIVE_CATEGORIES or plugin manager
- ServiceError.recovery field documented with Field description
- REQUIREMENTS.md marks ARCH-10, DEBT-05, DEBT-06 as complete
- All tests pass, mypy clean, ruff clean
</success_criteria>

<output>
After completion, create `.planning/phases/18-architecture-cleanup/18-01-SUMMARY.md`
</output>
