---
phase: 07-plugin-agentic-wiring-fixes
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
  - src/ztlctl/infrastructure/vault.py
  - src/ztlctl/mcp/response.py
  - src/ztlctl/mcp/generator.py
  - .planning/REQUIREMENTS.md
  - tests/mcp/test_response.py
  - tests/plugins/test_plugin_config.py
autonomous: true
requirements: [PLUG-03, AGNT-01, AGNT-04]

must_haves:
  truths:
    - "Plugins loaded from entry points receive validated TOML config during vault initialization"
    - "MCP error responses include full structured error detail from ServiceError"
    - "Category activation is documented as advisory metadata, not dynamic tool gating"
  artifacts:
    - path: "src/ztlctl/infrastructure/vault.py"
      provides: "inject_configs() call in init_event_bus()"
      contains: "pm.inject_configs(self._settings)"
    - path: "src/ztlctl/mcp/response.py"
      provides: "Error detail forwarding + ACTION_REJECTED recovery entry"
      contains: "detail=result.error.detail"
    - path: "src/ztlctl/mcp/generator.py"
      provides: "Advisory-only design documentation"
      contains: "advisory metadata"
  key_links:
    - from: "src/ztlctl/infrastructure/vault.py"
      to: "src/ztlctl/plugins/manager.py"
      via: "pm.inject_configs(self._settings)"
      pattern: "inject_configs"
    - from: "src/ztlctl/mcp/response.py"
      to: "src/ztlctl/services/result.py"
      via: "detail= forwarding in McpError construction"
      pattern: "detail=result\\.error\\.detail"
---

<objective>
Close three micro-fix integration gaps: wire plugin config injection into the vault initialization path (PLUG-03), forward ServiceError.detail to McpError.detail in MCP responses (AGNT-01), and document category activation as advisory metadata (AGNT-04).

Purpose: These three gaps are single-line or comment-level fixes that complete the wiring between fully-implemented components that were never connected in the production path.
Output: Updated vault.py, response.py, generator.py, REQUIREMENTS.md, plus tests confirming the fixes.
</objective>

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

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/07-plugin-agentic-wiring-fixes/07-CONTEXT.md
@.planning/phases/07-plugin-agentic-wiring-fixes/07-RESEARCH.md

<interfaces>
<!-- Key types and contracts the executor needs. -->

From src/ztlctl/plugins/manager.py:
```python
class PluginManager:
    def inject_configs(self, settings: ZtlSettings) -> None:
        """Validate TOML [plugins.<name>] sections and call plugin initialize()."""
```

From src/ztlctl/mcp/response.py:
```python
class McpError(BaseModel):
    code: str
    message: str
    recovery: str | None = None
    detail: dict[str, Any] = Field(default_factory=dict)

COMMON_ERROR_RECOVERY: dict[str, str] = { ... }
```

From src/ztlctl/services/result.py:
```python
class ServiceError(BaseModel):
    code: str
    message: str
    detail: dict[str, Any] = Field(default_factory=dict)
    recovery: str | None = None
```
</interfaces>
</context>

<tasks>

<task type="auto">
  <name>Task 1: Apply three micro-fixes (PLUG-03 + AGNT-01 + AGNT-04)</name>
  <files>src/ztlctl/infrastructure/vault.py, src/ztlctl/mcp/response.py, src/ztlctl/mcp/generator.py, .planning/REQUIREMENTS.md</files>
  <read_first>
    - src/ztlctl/infrastructure/vault.py (lines 361-390 — init_event_bus method)
    - src/ztlctl/mcp/response.py (full file — McpError model, COMMON_ERROR_RECOVERY dict, from_result method)
    - src/ztlctl/mcp/generator.py (lines 75-115 — _active_categories and related functions)
    - .planning/REQUIREMENTS.md (AGNT-04 entry to update)
  </read_first>
  <action>
**PLUG-03 — vault.py init_event_bus():**
Insert `pm.inject_configs(self._settings)` on a new line immediately after `pm.discover_and_load(local_dir=local_plugins)` (currently line 376) and before the `# Register built-in git plugin` comment block. Add a comment: `# PLUG-03: validate + pass TOML [plugins.<name>] config to third-party plugins`

The resulting code block should be:
```python
pm = PluginManager()
local_plugins = self.root / ".ztlctl" / "plugins"
pm.discover_and_load(local_dir=local_plugins)
# PLUG-03: validate + pass TOML [plugins.<name>] config to third-party plugins
pm.inject_configs(self._settings)

# Register built-in git plugin with vault context
git_config = self._settings.git
...
```

**AGNT-01 — response.py from_result():**
1. In the `McpError(...)` constructor call inside `from_result()` (around line 153-157), add `detail=result.error.detail,` as the last keyword argument, after `recovery=recovery,`.

2. Add `"ACTION_REJECTED"` to the `COMMON_ERROR_RECOVERY` dict with value: `"A plugin rejected this action. Check the error detail for the rejecting plugin and reason. Retry with different parameters or disable the plugin."`. Insert alphabetically among existing entries.

**AGNT-04 — generator.py advisory documentation:**
Add a multi-line comment block immediately after the `_active_categories` assignment (line 91: `_active_categories: set[str] = set(_DEFAULT_ACTIVE_CATEGORIES)`). The comment should read:

```python
# NOTE: Category activation state is advisory metadata for agent tool selection.
# FastMCP does not support dynamic tool deregistration without a server restart,
# so generate_tools() registers all ActionDefinitions unconditionally regardless
# of _active_categories. Agents use discover_categories to understand tool groupings
# and make informed tool selection decisions -- not to reduce the tool surface.
```

**AGNT-04 — REQUIREMENTS.md update:**
Find the AGNT-04 entry and update the description from:
`Progressive tool disclosure -- category-based tool activation so plugins don't overwhelm the MCP tool surface; agents can discover and activate tool categories on demand`
to:
`Progressive tool disclosure -- category-based discovery metadata so agents can understand tool groupings and make informed selection decisions; activation state is advisory (FastMCP does not support dynamic tool deregistration)`

Mark AGNT-04 as `[x]` (complete).
  </action>
  <verify>
    <automated>cd /Users/shparki/Documents/Workspace/thatdev/ztlctl && grep -n "inject_configs" src/ztlctl/infrastructure/vault.py && grep -n "detail=result.error.detail" src/ztlctl/mcp/response.py && grep -n "ACTION_REJECTED" src/ztlctl/mcp/response.py && grep -n "advisory metadata" src/ztlctl/mcp/generator.py</automated>
  </verify>
  <acceptance_criteria>
    - vault.py contains `pm.inject_configs(self._settings)` between `pm.discover_and_load(` and `git_config = self._settings.git`
    - response.py from_result() McpError constructor contains `detail=result.error.detail,`
    - response.py COMMON_ERROR_RECOVERY dict contains key `"ACTION_REJECTED"`
    - generator.py contains `# NOTE: Category activation state is advisory metadata`
    - REQUIREMENTS.md AGNT-04 line contains `[x]` and `advisory`
  </acceptance_criteria>
  <done>All three micro-fixes applied: inject_configs wired, error detail forwarded, advisory docs added, REQUIREMENTS.md updated</done>
</task>

<task type="auto">
  <name>Task 2: Tests for micro-fixes (PLUG-03 + AGNT-01)</name>
  <files>tests/mcp/test_response.py, tests/plugins/test_plugin_config.py</files>
  <read_first>
    - tests/mcp/test_response.py (full file — existing test patterns for McpResponse/McpError)
    - tests/plugins/test_plugin_config.py (full file — existing test patterns for inject_configs)
    - src/ztlctl/mcp/response.py (from_result implementation after Task 1 changes)
    - src/ztlctl/infrastructure/vault.py (init_event_bus after Task 1 changes)
  </read_first>
  <action>
**test_response.py — Add test for AGNT-01 detail forwarding:**
Add a test function `test_from_result_forwards_error_detail()` that:
1. Creates a `ServiceResult(ok=False, op="test_op", error=ServiceError(code="NOT_FOUND", message="gone", detail={"id": "ZTL-001", "searched": ["nodes", "fts"]}))`.
2. Calls `McpResponse.from_result(result)`.
3. Asserts `response.error is not None`.
4. Asserts `response.error.detail == {"id": "ZTL-001", "searched": ["nodes", "fts"]}`.

Add a second test function `test_from_result_forwards_empty_detail()` that:
1. Creates a `ServiceResult(ok=False, op="test_op", error=ServiceError(code="NOT_FOUND", message="gone"))` (no detail= kwarg, defaults to `{}`).
2. Calls `McpResponse.from_result(result)`.
3. Asserts `response.error.detail == {}`.

Add a third test function `test_action_rejected_in_common_error_recovery()` that:
1. Imports `COMMON_ERROR_RECOVERY` from `ztlctl.mcp.response`.
2. Asserts `"ACTION_REJECTED" in COMMON_ERROR_RECOVERY`.
3. Asserts the value contains `"plugin"` (case-insensitive check with `.lower()`).

**test_plugin_config.py — Add test for PLUG-03 vault wiring:**
Add a test function `test_init_event_bus_calls_inject_configs()` that:
1. Creates a temporary vault using the existing test fixture pattern in that file.
2. Uses `unittest.mock.patch.object` on `PluginManager` to mock `inject_configs`.
3. Calls `vault.init_event_bus(sync=True)`.
4. Asserts `mock_inject_configs.called` is True.
5. Asserts the first positional argument to the call is the vault's settings object (i.e., `mock_inject_configs.call_args[0][0]` is the settings instance, or check `call_args.args[0]`).

Follow the existing import patterns and fixture usage in each test file.
  </action>
  <verify>
    <automated>cd /Users/shparki/Documents/Workspace/thatdev/ztlctl && uv run pytest tests/mcp/test_response.py tests/plugins/test_plugin_config.py -x -q</automated>
  </verify>
  <acceptance_criteria>
    - tests/mcp/test_response.py contains `def test_from_result_forwards_error_detail`
    - tests/mcp/test_response.py contains `def test_from_result_forwards_empty_detail`
    - tests/mcp/test_response.py contains `def test_action_rejected_in_common_error_recovery`
    - tests/plugins/test_plugin_config.py contains `def test_init_event_bus_calls_inject_configs`
    - `uv run pytest tests/mcp/test_response.py tests/plugins/test_plugin_config.py -x` exits 0
  </acceptance_criteria>
  <done>All four tests pass, confirming PLUG-03 vault wiring and AGNT-01 detail forwarding work correctly</done>
</task>

</tasks>

<verification>
- `grep -n "inject_configs" src/ztlctl/infrastructure/vault.py` shows the call in init_event_bus
- `grep -n "detail=result.error.detail" src/ztlctl/mcp/response.py` shows the forwarding
- `grep -n "advisory metadata" src/ztlctl/mcp/generator.py` shows the documentation
- `uv run pytest tests/mcp/test_response.py tests/plugins/test_plugin_config.py -x -q` all pass
- `uv run ruff check src/ztlctl/infrastructure/vault.py src/ztlctl/mcp/response.py src/ztlctl/mcp/generator.py` clean
</verification>

<success_criteria>
- inject_configs() is called during vault initialization after discover_and_load() and before built-in plugin registration
- ServiceError.detail is forwarded to McpError.detail in all MCP error responses
- ACTION_REJECTED has a COMMON_ERROR_RECOVERY entry
- Category activation documented as advisory-only in generator.py and REQUIREMENTS.md
- All new tests pass
</success_criteria>

<output>
After completion, create `.planning/phases/07-plugin-agentic-wiring-fixes/07-01-SUMMARY.md`
</output>
