---
phase: 11-manual-ruff-cli-mypy
plan: 03
type: execute
wave: 1
depends_on: []
files_modified:
  - src/corpus_analyzer/llm/chunked_processor.py
autonomous: true
requirements:
  - MYPY-02

must_haves:
  truths:
    - "`uv run mypy src/ --no-error-summary 2>&1 | grep 'chunked_processor.py'` returns empty (zero errors)"
    - "`Atom` dataclass is defined at module level (before `split_on_headings`)"
    - "Nested functions `finalize_atom`, `get_chunk_text`, `chain_lines` have full type signatures"
    - "All 281 tests still pass — no logic changes, only structural promotion and annotation"
  artifacts:
    - path: "src/corpus_analyzer/llm/chunked_processor.py"
      provides: "Mypy-clean chunked processor (zero errors)"
  key_links:
    - from: "src/corpus_analyzer/llm/chunked_processor.py"
      to: "Atom dataclass"
      via: "module-level promotion from nested definition"
      pattern: "^@dataclass\\nclass Atom"
---

<objective>
Fix all 12 mypy errors in `llm/chunked_processor.py` by: (1) promoting the `Atom` dataclass from inside `split_on_headings` to module level, and (2) adding full type annotations to the three nested functions `finalize_atom`, `get_chunk_text`, and `chain_lines`.

Purpose: MYPY-02 — chunked_processor.py has 12 errors, all caused by untyped nested functions and the nested dataclass that mypy cannot analyse in a typed context.
Output: A mypy-clean chunked_processor.py. No logic changes — only structural promotion and annotation.
</objective>

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

<context>
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/11-manual-ruff-cli-mypy/11-RESEARCH.md
</context>

<tasks>

<task type="auto">
  <name>Task 1: Promote Atom dataclass to module level</name>
  <files>src/corpus_analyzer/llm/chunked_processor.py</files>
  <action>
Read the full file first. The `Atom` dataclass is currently defined inside `split_on_headings`. Move it to module level (before the function definition).

**Step 1:** Find the nested `@dataclass class Atom:` definition inside `split_on_headings`. Note all its fields.

**Step 2:** Remove the nested class definition from inside the function.

**Step 3:** Insert the class at module level, before `split_on_headings`, with a PEP 257 docstring:

```python
@dataclass
class Atom:
    """Atomic block unit used by split_on_headings."""

    lines: list[str]
    heading: str | None
    level: int
    is_code: bool
    start_idx: int
```

Verify `dataclass` is imported at the top of the file (`from dataclasses import dataclass`). Add the import if missing.

**Step 4:** Inside `split_on_headings`, the `nonlocal` references to mutable state (e.g., `current_atom`, `atoms`) must stay intact. The promotion only removes the class definition — all usage of `Atom(...)` constructor calls inside the function remain unchanged.

After this edit, run `uv run pytest -x -q 2>&1 | tail -5` to confirm no regressions before proceeding to annotations.
  </action>
  <verify>
    <automated>uv run pytest -x -q 2>&1 | tail -5</automated>
    <manual>Confirm `class Atom` appears before `def split_on_headings` in the file.</manual>
  </verify>
  <done>`Atom` is at module level; all tests pass.</done>
</task>

<task type="auto">
  <name>Task 2: Annotate nested functions finalize_atom, get_chunk_text, chain_lines</name>
  <files>src/corpus_analyzer/llm/chunked_processor.py</files>
  <action>
Add full type annotations to the three nested functions inside `split_on_headings`. Read the current function bodies to understand parameter usage before annotating.

**`finalize_atom`:** This function finalises the current atom and appends it to the atoms list. It takes optional override parameters and mutates `nonlocal` state. Based on research:
```python
def finalize_atom(
    force_heading: str | None = None,
    force_level: int = 0,
    is_code_atom: bool = False,
) -> None:
    ...
```
The `nonlocal` statement(s) inside the body must remain as-is.

**`get_chunk_text`:** Takes a list of Atom objects and returns their combined text as a string:
```python
def get_chunk_text(atoms_list: list[Atom]) -> str:
    ...
```

**`chain_lines`:** Takes a list of Atom objects and returns a flat list of lines:
```python
def chain_lines(atoms_list: list[Atom]) -> list[str]:
    ...
```

Read the actual function bodies to confirm the parameter names match what the research document describes. If parameter names differ, use the actual names from the source file and apply the matching types.

After annotating, run mypy on the file:
```bash
uv run mypy src/ --no-error-summary 2>&1 | grep "chunked_processor.py"
```
Must return empty. Then run the full test suite.
  </action>
  <verify>
    <automated>uv run mypy src/ --no-error-summary 2>&1 | grep "chunked_processor.py" | wc -l | grep -q "^0$" && echo "chunked_processor.py: 0 errors" && uv run pytest -x -q 2>&1 | tail -3</automated>
  </verify>
  <done>Zero mypy errors in chunked_processor.py; all 281 tests pass.</done>
</task>

</tasks>

<verification>
After both tasks:
1. `uv run mypy src/ --no-error-summary 2>&1 | grep "chunked_processor.py"` — empty
2. `class Atom` is at module level in chunked_processor.py
3. `finalize_atom`, `get_chunk_text`, `chain_lines` all have full signatures
4. `uv run pytest -x -q` — all 281 tests green
</verification>

<success_criteria>
- Zero mypy errors in llm/chunked_processor.py
- Atom promoted to module level
- All nested functions fully annotated
- All 281 tests pass
</success_criteria>

<output>
After completion, create `.planning/phases/11-manual-ruff-cli-mypy/11-03-SUMMARY.md`
</output>
