---
phase: 11-developer-guide-api-reference
plan: 02
type: execute
wave: 1
depends_on: []
files_modified:
  - docs/plugin-guide.md
autonomous: true
requirements:
  - DVGD-01

must_haves:
  truths:
    - "A plugin author can create a working plugin class with PLUGIN_API_VERSION = 1 by following the tutorial"
    - "Every hookspec has its signature, return type, and behavior documented in the reference section"
    - "The complete working example plugin (MyVaultPlugin) is included inline with pyproject.toml entry point"
    - "Custom NoteTypeDefinition registration is demonstrated with a concrete example"
    - "Deprecated per-event hooks have a migration guide pointing to post_action"
  artifacts:
    - path: "docs/plugin-guide.md"
      provides: "Tutorial + hookspec reference for plugin authors"
      min_lines: 250
      contains: "PLUGIN_API_VERSION"
  key_links:
    - from: "docs/plugin-guide.md tutorial section"
      to: "src/ztlctl/plugins/builtins/git.py"
      via: "inline code examples based on real plugin patterns"
      pattern: "PLUGIN_API_VERSION = 1"
    - from: "docs/plugin-guide.md hookspec reference"
      to: "src/ztlctl/plugins/hookspecs.py"
      via: "16 hookspecs with exact signatures from source"
      pattern: "pre_action|post_action|declare_capabilities"
---

<objective>
Create `docs/plugin-guide.md` — a complete plugin authoring guide with a step-by-step tutorial ("Build Your First Plugin") followed by a hookspec reference covering all 16 hookspecs. All hookspec signatures must match the source of truth in hookspecs.py exactly.

Purpose: Satisfies DVGD-01. Enables plugin authors to go from zero to a working plugin without reading source code.
Output: docs/plugin-guide.md (~300+ lines, single file)
</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/phases/11-developer-guide-api-reference/11-RESEARCH.md
@.planning/phases/11-developer-guide-api-reference/11-CONTEXT.md
</context>

<tasks>

<task type="auto">
  <name>Task 1: Create docs/plugin-guide.md with tutorial and hookspec reference</name>
  <files>docs/plugin-guide.md</files>
  <read_first>
    - src/ztlctl/plugins/hookspecs.py (all 291 lines — authoritative hookspec signatures)
    - src/ztlctl/plugins/contracts.py (all 237 lines — ActionRejection, contribution contracts, PluginMetadata)
    - src/ztlctl/plugins/_version.py (all 64 lines — PLUGIN_API_VERSION, PluginLoadError, check_plugin_api_version)
    - src/ztlctl/plugins/builtins/git.py (full — real tutorial example: PLUGIN_API_VERSION=1, hookimpl, post_action, declare_capabilities)
    - src/ztlctl/plugins/builtins/reweave_plugin.py (full — post_action filtering pattern)
    - src/ztlctl/domain/registry.py (NoteTypeDefinition dataclass — for register_note_types example)
  </read_first>
  <action>
Create `docs/plugin-guide.md` with the following structure. All code snippets must use exact signatures from source — read the files above before writing.

**Structure:**

```
# Plugin Authoring Guide

## Overview
Brief description: pluggy-based system, entry-point discovery, PLUGIN_API_VERSION=1 compatibility contract.

## Tutorial: Build Your First Plugin

### 1. Create the Plugin Package
Directory layout: `my_vault_plugin/__init__.py` + `pyproject.toml`
Show: `pip install pluggy pydantic` as deps; `pluggy.HookimplMarker("ztlctl")` pattern.

### 2. Add PLUGIN_API_VERSION
```python
class MyVaultPlugin:
    PLUGIN_API_VERSION = 1
```
Explain: PLUGIN_API_VERSION = 1 matches current host. Compatibility window = 2 versions.

### 3. Implement post_action
Show full MyVaultPlugin with post_action hook filtering on action_name == "create_note".
Use the complete working example from RESEARCH.md (reproduced below with read_first verification).

### 4. Declare Capabilities
Show declare_capabilities returning {"network"}. Explain the 4 valid values: filesystem, network, database, git.

### 5. Add a Custom Note Type (NoteTypeDefinition)
Show register_note_types() returning a NoteTypeDefinition with:
- name="sprint", content_type="note", model_cls=NoteModel
- transitions={"active": ["completed", "cancelled"], "completed": [], "cancelled": []}
- template_name="sprint.md.j2", required_sections=["## Goal", "## Stories"]
Explain: PluginManager auto-creates CRUD ActionDefinitions for registered note types.

### 6. Add Plugin Config (get_config_schema + initialize)
Show MyPluginConfig(BaseModel) with webhook_url: str = "" and enabled: bool = True.
Show get_config_schema() returning MyPluginConfig.
Show initialize(config) storing validated config.
Show matching [plugins.my-plugin] section in .ztlctl/config.toml.

### 7. Register via Entry Point
Show pyproject.toml:
```toml
[project.entry-points."ztlctl.plugins"]
my-plugin = "my_vault_plugin:MyVaultPlugin"
```
Explain: ztlctl discovers plugins via importlib.metadata on startup.

### 8. Test Your Plugin
Show pytest test that instantiates the plugin and asserts post_action behavior.
Note: pluggy's call_historic / hook calling can be tested without a real vault.
```

---

**Hookspec Reference**

## Hookspec Reference

Intro paragraph: All hooks use `pluggy.HookimplMarker("ztlctl")`. Implement only the hooks you need.

### Generic Action Hooks (Preferred)

**`pre_action(action_name: str, kwargs: dict[str, Any]) -> ActionRejection | dict[str, Any] | None`**
- `firstresult=True` — first non-None return wins
- Return `ActionRejection(reason=..., code=..., detail={})` to abort the action
- Return modified `kwargs` dict to transform inputs before the action runs
- Return `None` to pass through without modification
- Fires before every registered action

**`post_action(action_name: str, kwargs: dict[str, Any], result: Any) -> None`**
- `firstresult=False` — all plugins receive the call
- Called after every action regardless of outcome
- Filter on `action_name` to scope your plugin's behavior
- `result` is the ServiceResult object (or None on error)

### Plugin Lifecycle Hooks

**`get_config_schema() -> type[BaseModel] | None`**
- `firstresult=True`
- Return a Pydantic BaseModel class (not an instance) for config validation
- Called once at plugin load time

**`initialize(config: BaseModel | None) -> None`**
- Called once after `get_config_schema()`, with the validated config instance (or None if no config)
- Store the config reference on self for use in other hooks

### Extension Contribution Hooks

Document each of the 11 register_* hooks in a table with: hookspec name, return type, what it adds.
Use exact return types from RESEARCH.md:
- register_content_models → dict[str, type[ContentModel]] | None
- register_cli_commands → list[CliCommandContribution] | None
- register_mcp_tools → list[McpToolContribution] | None
- register_mcp_resources → list[McpResourceContribution] | None
- register_mcp_prompts → list[McpPromptContribution] | None
- register_workflow_modules → list[WorkflowModuleContribution] | None
- register_workspace_profiles → list[WorkspaceProfileContribution] | None
- register_vault_init_steps → list[VaultInitStepContribution] | None
- register_source_providers → list[SourceProviderContribution] | None
- register_note_types → list[NoteTypeDefinition] | None
- register_render_contributions → list[RenderContribution] | None

### Security — Capability Declarations

`declare_capabilities() -> set[str] | None`
Valid values: `{"filesystem", "network", "database", "git"}`.
PluginManager warns if a capability is used without declaration.

### Deprecated Per-Event Hooks

Table of deprecated hooks with migration guidance:
| Deprecated Hook | Use Instead |
|---|---|
| post_create(content_type, content_id, title, path, tags) | post_action + filter action_name in {"create_note", "create_reference", "create_task", "create_log"} |
| post_update(content_type, content_id, fields_changed, path) | post_action + filter action_name.startswith("update_") |
| post_close(content_type, content_id, path, summary) | post_action + filter action_name.endswith("_close") |
| post_reweave(source_id, affected_ids, links_added) | post_action + filter action_name == "reweave" |
| post_session_start(session_id) | post_action + filter action_name == "session_start" |
| post_session_close(session_id, stats) | post_action + filter action_name == "session_close" |
| post_check(issues_found, issues_fixed) | post_action + filter action_name == "check" |
| post_init(vault_name, client, tone) | post_action + filter action_name == "init" |
| post_init_profile(vault_name, profile, tone, managed_paths) | post_action + filter action_name == "init_profile" |

Note: Deprecated hooks emit `DeprecationWarning` since plugin API v2. They will be removed in a future API version.

---

## Plugin Metadata (PluginMetadata)

For marketplace listing, declare in pyproject.toml:
```toml
[tool.ztlctl-plugin]
name = "my-vault-plugin"
version = "1.0.0"
author = "Your Name"
description = "What this plugin does"
ztlctl_api_version = 1
capabilities = ["network"]
```

Corresponds to `PluginMetadata(name, version, author, capabilities, ztlctl_api_version, description="")`.

---

## Compatibility and Versioning

- Current PLUGIN_API_VERSION: 1
- Compatibility window: 2 (plugins declaring versions ≤ PLUGIN_API_VERSION - 2 are rejected)
- Plugins 1-2 versions behind current get a deprecation warning but load successfully
- Missing PLUGIN_API_VERSION attribute: treated as legacy (loads without warning)
- PluginLoadError raised for incompatible versions

---

**Critical accuracy requirements:**
- All hookspec signatures must match hookspecs.py exactly — read the file before writing
- The complete MyVaultPlugin code example must be self-contained and runnable
- Deprecated hook parameter names must match hookspecs.py (not guessed)
- NoteTypeDefinition fields must match domain/registry.py (not guessed)
  </action>
  <verify>
    <automated>cd /Users/shparki/Documents/Workspace/thatdev/ztlctl && test -f docs/plugin-guide.md && wc -l docs/plugin-guide.md && grep -c "PLUGIN_API_VERSION\|post_action\|pre_action\|declare_capabilities\|register_note_types" docs/plugin-guide.md</automated>
  </verify>
  <done>
docs/plugin-guide.md exists with 250+ lines.
Contains all 16 hookspecs (2 generic action + 2 lifecycle + 11 extension + 1 security).
Contains deprecated hook migration table (9 hooks).
Contains complete MyVaultPlugin code example with pyproject.toml entry point.
Contains NoteTypeDefinition example with sprint type.
Contains PluginMetadata section.
  </done>
</task>

</tasks>

<verification>
- `test -f docs/plugin-guide.md` passes
- `wc -l docs/plugin-guide.md` returns 250+
- `grep "PLUGIN_API_VERSION" docs/plugin-guide.md` matches
- `grep "register_note_types\|declare_capabilities\|pre_action\|post_action" docs/plugin-guide.md` returns 4+ matches
- `grep "PluginMetadata\|Deprecated" docs/plugin-guide.md` matches both
</verification>

<success_criteria>
docs/plugin-guide.md is a complete, accurate plugin authoring guide. A plugin author can follow the tutorial to create a working plugin from scratch, and can use the hookspec reference to find any hook's exact signature, return type, and behavior. All signatures match hookspecs.py.
</success_criteria>

<output>
After completion, create `.planning/phases/11-developer-guide-api-reference/11-02-SUMMARY.md`
</output>
