---
phase: 19-methodology-guidance-and-polaris
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
  - src/ztlctl/services/init.py
  - src/ztlctl/templates/self/polaris.md.j2
  - src/ztlctl/mcp/resources.py
  - src/ztlctl/services/context.py
  - tests/services/test_init.py
  - tests/mcp/test_resources.py
  - tests/services/test_context.py
autonomous: true
requirements: [POLR-01, POLR-02, POLR-03]

must_haves:
  truths:
    - "A new vault created with ztlctl init contains garden/groves/polaris.md with starter priorities template"
    - "An MCP agent reading ztlctl://polaris receives the polaris document content"
    - "ContextAssembler includes polaris content in Layer 1 (operational state) with 500-token budget"
  artifacts:
    - path: "src/ztlctl/templates/self/polaris.md.j2"
      provides: "Jinja2 template for polaris starter document"
      contains: "Mission"
    - path: "src/ztlctl/mcp/resources.py"
      provides: "polaris_impl function and ztlctl://polaris resource registration"
      contains: "polaris_impl"
    - path: "src/ztlctl/services/context.py"
      provides: "Polaris content in Layer 1 assembly"
      contains: "polaris"
  key_links:
    - from: "src/ztlctl/services/init.py"
      to: "src/ztlctl/templates/self/polaris.md.j2"
      via: "Jinja2 render during init_vault step 5"
      pattern: "polaris"
    - from: "src/ztlctl/mcp/resources.py"
      to: "garden/groves/polaris.md"
      via: "file read from vault root"
      pattern: "garden.*groves.*polaris"
    - from: "src/ztlctl/services/context.py"
      to: "garden/groves/polaris.md"
      via: "file read with token budgeting in Layer 1"
      pattern: "polaris"
---

<objective>
Scaffold the polaris priorities document during vault init, expose it as an MCP resource, and integrate it into the ContextAssembler Layer 1 with token budgeting.

Purpose: Make ztlctl a priorities-aware memory system where agents can read the user's polaris document at context assembly time and through a dedicated MCP resource.
Output: polaris.md.j2 template, init scaffolding, MCP resource, context assembler integration, tests.
</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
@.planning/phases/19-methodology-guidance-and-polaris/19-CONTEXT.md

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

From src/ztlctl/services/init.py (init_vault pipeline):
```python
# Step 2 creates directories (line ~360-370):
dirs = [vault_path / ".ztlctl", vault_path / "self", vault_path / "notes", ...]
# Step 5 renders self/ files (line ~392-405):
rendered = _render_self_files(vault_name=name, tone=tone, profile=profile, client=legacy_client, topics=topics, created=created, vault_root=vault_path)
self_dir = vault_path / "self"
for filename, content in rendered.items():
    (self_dir / filename).write_text(content, encoding="utf-8")
    files_created.append(f"self/{filename}")
```

From src/ztlctl/mcp/resources.py (resource pattern):
```python
# _impl function pattern (line ~93-106):
def self_identity_impl(vault: Any) -> str:
    path = vault.root / "self" / "identity.md"
    if path.exists():
        return str(path.read_text(encoding="utf-8"))
    return "No identity file found. Run `ztlctl init` to generate one."

# Registration pattern (line ~659-663):
@server.resource("ztlctl://self/identity")  # type: ignore[untyped-decorator]
def identity_resource() -> str:
    return self_identity_impl(vault)

# _RESOURCE_CATALOG tuple at module top for catalog listing
```

From src/ztlctl/services/context.py (Layer 1 pattern, line ~95-127):
```python
# Layer 1: Operational State (always)
with trace_span("layer_1_operational") as span:
    layers["session"] = {...}
    layer_1_tokens = estimate_tokens(json.dumps(layers["session"]))
    layers["recent_decisions"] = self._recent_decisions(warnings)
    # ... token counting per item ...
    token_count += layer_1_tokens
```

From src/ztlctl/services/_helpers.py:
```python
def estimate_tokens(text: str) -> int:
```
</interfaces>
</context>

<tasks>

<task type="auto" tdd="true">
  <name>Task 1: Polaris template + init scaffolding + MCP resource</name>
  <files>
    src/ztlctl/templates/self/polaris.md.j2
    src/ztlctl/services/init.py
    src/ztlctl/mcp/resources.py
    tests/services/test_init.py
    tests/mcp/test_resources.py
  </files>
  <read_first>
    src/ztlctl/services/init.py
    src/ztlctl/templates/self/methodology.md.j2
    src/ztlctl/mcp/resources.py
    tests/services/test_init.py
    tests/mcp/test_resources.py
    src/ztlctl/plugins/builtins/obsidian.py
  </read_first>
  <behavior>
    - Test: polaris.md.j2 renders a document with "Mission", "Current Priorities", "Decision Principles" sections
    - Test: init_vault creates garden/groves/polaris.md in the vault
    - Test: files_created list in ServiceResult includes "garden/groves/polaris.md"
    - Test: polaris_impl returns polaris document content when file exists
    - Test: polaris_impl returns guidance message when file does not exist
    - Test: resource_catalog includes ztlctl://polaris entry
  </behavior>
  <action>
    1. Create `src/ztlctl/templates/self/polaris.md.j2` with starter template (per CONTEXT.md decisions):
       - Frontmatter: `generated: true`, `vault: "{{ vault_name }}"`, `created: {{ created }}`
       - Three sections: `## Mission` (placeholder for user's mission), `## Current Priorities` (numbered 1-3 with placeholder text), `## Decision Principles` (3 bullet-point placeholders)
       - Research-partner tone: "Your polaris is your north star. Update it as your priorities evolve."

    2. Modify `src/ztlctl/services/init.py`:
       - In `init_vault` Step 2 (CREATE STRUCTURE), add `vault_path / "garden" / "groves"` to the `dirs` list
       - After Step 5 (RENDER SELF), add a new step 5b to render and write polaris:
         ```python
         # 5b. RENDER POLARIS
         polaris_env = build_template_environment("self", vault_root=vault_path)
         polaris_content = polaris_env.get_template("polaris.md.j2").render(
             vault_name=name, created=created
         )
         polaris_dir = vault_path / "garden" / "groves"
         polaris_dir.mkdir(parents=True, exist_ok=True)
         (polaris_dir / "polaris.md").write_text(polaris_content, encoding="utf-8")
         files_created.append("garden/groves/polaris.md")
         ```

    3. Modify `src/ztlctl/mcp/resources.py`:
       - Add `{"uri": "ztlctl://polaris", "description": "The vault's polaris priorities document."}` to `_RESOURCE_CATALOG` tuple
       - Add `polaris_impl` function following the `self_identity_impl` pattern:
         ```python
         def polaris_impl(vault: Any) -> str:
             path = vault.root / "garden" / "groves" / "polaris.md"
             if path.exists():
                 return str(path.read_text(encoding="utf-8"))
             return "No polaris file found. Run `ztlctl init` to generate one, or create garden/groves/polaris.md manually."
         ```
       - In `register_resources`, add:
         ```python
         @server.resource("ztlctl://polaris")  # type: ignore[untyped-decorator]
         def polaris_resource() -> str:
             return polaris_impl(vault)
         ```

    4. Add tests in `tests/services/test_init.py` for polaris scaffolding (assert garden/groves/polaris.md exists after init_vault, check files_created includes it).
    5. Add tests in `tests/mcp/test_resources.py` for `polaris_impl` (exists and not-exists cases), and verify `resource_catalog` includes `ztlctl://polaris`.
  </action>
  <verify>
    <automated>uv run pytest tests/services/test_init.py -x -q && uv run pytest tests/mcp/test_resources.py -x -q</automated>
  </verify>
  <acceptance_criteria>
    - grep "polaris.md.j2" src/ztlctl/templates/self/polaris.md.j2 finds the file
    - grep "Mission" src/ztlctl/templates/self/polaris.md.j2 finds the section
    - grep "Current Priorities" src/ztlctl/templates/self/polaris.md.j2 finds the section
    - grep "Decision Principles" src/ztlctl/templates/self/polaris.md.j2 finds the section
    - grep "garden.*groves" src/ztlctl/services/init.py confirms directory creation
    - grep "polaris_impl" src/ztlctl/mcp/resources.py confirms resource implementation
    - grep "ztlctl://polaris" src/ztlctl/mcp/resources.py confirms catalog entry
  </acceptance_criteria>
  <done>
    - polaris.md.j2 template exists with Mission, Current Priorities, Decision Principles sections
    - init_vault scaffolds garden/groves/polaris.md and includes it in files_created
    - ztlctl://polaris MCP resource registered with polaris_impl function
    - All tests pass
  </done>
</task>

<task type="auto" tdd="true">
  <name>Task 2: ContextAssembler polaris integration in Layer 1</name>
  <files>
    src/ztlctl/services/context.py
    tests/services/test_context.py
  </files>
  <read_first>
    src/ztlctl/services/context.py
    src/ztlctl/services/_helpers.py
    tests/services/test_context.py
  </read_first>
  <behavior>
    - Test: assemble() includes "polaris" key in layers when polaris file exists
    - Test: polaris content is truncated to 500-token budget
    - Test: assemble() sets polaris to None when file does not exist
    - Test: polaris tokens are counted toward layer_1_tokens total
  </behavior>
  <action>
    1. Modify `src/ztlctl/services/context.py` in the `assemble` method, inside the Layer 1 block (after session, recent_decisions, work_queue, log_entries — before `token_count += layer_1_tokens`):
       - Add polaris content reading with 500-token budget:
         ```python
         # Polaris priorities (token-budgeted)
         polaris_path = self._vault.root / "garden" / "groves" / "polaris.md"
         if polaris_path.exists():
             polaris_raw = polaris_path.read_text(encoding="utf-8")
             polaris_tokens = estimate_tokens(polaris_raw)
             if polaris_tokens > 500:
                 # Truncate to ~500 tokens (rough char estimate: 500 * 4 = 2000 chars)
                 char_limit = 500 * 4
                 polaris_raw = polaris_raw[:char_limit] + "\n\n[... polaris truncated to fit token budget ...]"
                 polaris_tokens = 500
             layers["polaris"] = polaris_raw
             layer_1_tokens += polaris_tokens
         else:
             layers["polaris"] = None
         ```

    2. Add tests in `tests/services/test_context.py`:
       - Create a vault fixture with `garden/groves/polaris.md` containing sample priorities
       - Assert `assemble()` result data includes `polaris` key with content
       - Test with no polaris file: `polaris` is None
       - Test with large polaris content (> 2000 chars): content is truncated with `[... polaris truncated` marker
  </action>
  <verify>
    <automated>uv run pytest tests/services/test_context.py -x -q -k polaris</automated>
  </verify>
  <acceptance_criteria>
    - grep "polaris" src/ztlctl/services/context.py finds polaris integration in Layer 1
    - grep "garden.*groves.*polaris" src/ztlctl/services/context.py confirms file path
    - grep "500" src/ztlctl/services/context.py confirms token budget constant
    - grep "polaris" tests/services/test_context.py finds test coverage
  </acceptance_criteria>
  <done>
    - ContextAssembler.assemble() reads polaris from garden/groves/polaris.md into Layer 1
    - Polaris content budget-capped at 500 tokens
    - Polaris set to None when file does not exist
    - All polaris-related tests pass
  </done>
</task>

</tasks>

<verification>
1. `uv run pytest tests/services/test_init.py tests/mcp/test_resources.py tests/services/test_context.py -x -q` — all tests pass
2. `uv run ruff check src/ztlctl/services/init.py src/ztlctl/mcp/resources.py src/ztlctl/services/context.py` — no lint errors
3. `uv run mypy src/ztlctl/services/init.py src/ztlctl/mcp/resources.py src/ztlctl/services/context.py` — no type errors
</verification>

<success_criteria>
- Polaris template exists at src/ztlctl/templates/self/polaris.md.j2 with 3 sections
- init_vault creates garden/groves/polaris.md
- ztlctl://polaris MCP resource returns polaris content
- ContextAssembler Layer 1 includes polaris with 500-token budget
- All tests pass, lint clean, types clean
</success_criteria>

<output>
After completion, create `.planning/phases/19-methodology-guidance-and-polaris/19-01-SUMMARY.md`
</output>
