---
phase: 10-manual-ruff-leaf-to-hub
plan: 02
type: execute
wave: 2
depends_on:
  - 10-01
files_modified:
  - src/corpus_analyzer/classifiers/document_type.py
  - src/corpus_analyzer/analyzers/quality.py
  - src/corpus_analyzer/analyzers/shape.py
  - src/corpus_analyzer/utils/ui.py
  - src/corpus_analyzer/search/engine.py
  - src/corpus_analyzer/ingest/chunker.py
  - src/corpus_analyzer/ingest/indexer.py
autonomous: true
requirements:
  - RUFF-03

must_haves:
  truths:
    - "All E501 violations in classifiers/, analyzers/, utils/, search/, and ingest/ are resolved"
    - "All 281 tests pass after structural line-wrapping (no semantic changes)"
    - "No regex pattern strings are altered — only call structure is wrapped"
  artifacts:
    - path: "src/corpus_analyzer/classifiers/document_type.py"
      provides: "E501-clean — 9 long lines wrapped across 11 violations"
      min_lines: 200
    - path: "src/corpus_analyzer/analyzers/shape.py"
      provides: "E501-clean — f-string expression extracted to local variable"
      contains: "depth_rows"
    - path: "src/corpus_analyzer/analyzers/quality.py"
      provides: "E501-clean — update_document_quality call wrapped"
    - path: "src/corpus_analyzer/utils/ui.py"
      provides: "E501-clean — 3 long lines wrapped"
    - path: "src/corpus_analyzer/search/engine.py"
      provides: "E501-clean — 1 long line wrapped"
    - path: "src/corpus_analyzer/ingest/chunker.py"
      provides: "E501-clean — 1 long line wrapped"
    - path: "src/corpus_analyzer/ingest/indexer.py"
      provides: "E501-clean — 1 long line wrapped"
  key_links:
    - from: "src/corpus_analyzer/analyzers/shape.py"
      to: "f-string template"
      via: "extracted local variable `depth_rows`"
      pattern: "depth_rows"
    - from: "src/corpus_analyzer/classifiers/document_type.py"
      to: "CLASSIFICATION_RULES constant"
      via: "type annotation wrapped across lines"
      pattern: "CLASSIFICATION_RULES: list\\["
---

<objective>
Wrap all E501 line-length violations in leaf modules: classifiers, analyzers, utils, search, and ingest directories. All fixes are purely structural (line wrapping) — no content changes, no regex alterations, no logic changes.

Purpose: Clears 17 of the 29 E501 violations in src/, leaving only generators/ and database.py for Plan 03.
Output: 7 source files with zero E501 violations; 281 tests still passing.
</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/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/10-manual-ruff-leaf-to-hub/10-RESEARCH.md
@.planning/phases/10-manual-ruff-leaf-to-hub/10-01-SUMMARY.md
</context>

<tasks>

<task type="auto">
  <name>Task 1: Wrap E501 violations in classifiers/document_type.py</name>
  <files>
    src/corpus_analyzer/classifiers/document_type.py
  </files>
  <action>
Fix 9 E501 violations in classifiers/document_type.py. All are structural wraps — do NOT alter string content, regex patterns, or logic.

**Lines 75-82, 87** — long `re.search` / `re.findall` calls. Wrap each by parenthesising the expression and splitting arguments:
```python
# Pattern (apply to all):
features.X = len(
    re.findall(r'<pattern>', features.full_text, re.IGNORECASE)
)
```
Each call fits into 3 lines using this form. Keep regex string on a single line inside the parenthesised call. Do NOT split the regex string itself.

**Line 116** — long `idf = math.log(...)` expression. Wrap the math.log call arguments:
```python
# Typical form — wrap into:
idf = math.log(
    <argument1> / <argument2>
)
```

**Line 125** — long type annotation for `CLASSIFICATION_RULES`. Wrap the type across lines:
```python
CLASSIFICATION_RULES: list[
    tuple[DocumentCategory, float, list[str], list[str], dict[str, float]]
] = [
```

**Line 210** — long `for` loop unpacking or list comprehension line. Read the actual line and wrap using parentheses if it is a condition or call, or a continuation backslash into a parenthesised expression.

**Line 234** — long conditional in an `if` statement. Wrap using parentheses:
```python
if (
    feature_name in [...]
):
```

After editing, check this file specifically:
```bash
uv run ruff check src/corpus_analyzer/classifiers/document_type.py
uv run pytest tests/test_classifiers/ -v --tb=short -q 2>&1 | tail -10
```
  </action>
  <verify>
    <automated>uv run ruff check src/corpus_analyzer/classifiers/document_type.py && uv run pytest tests/test_classifiers/ -v --tb=short -q 2>&1 | tail -5</automated>
    <manual>Confirm zero E501 violations in document_type.py. Confirm classifier tests pass.</manual>
  </verify>
  <done>Zero ruff violations in classifiers/document_type.py; classifier tests pass.</done>
</task>

<task type="auto">
  <name>Task 2: Wrap E501 violations in analyzers/, utils/, search/, and ingest/</name>
  <files>
    src/corpus_analyzer/analyzers/quality.py
    src/corpus_analyzer/analyzers/shape.py
    src/corpus_analyzer/utils/ui.py
    src/corpus_analyzer/search/engine.py
    src/corpus_analyzer/ingest/chunker.py
    src/corpus_analyzer/ingest/indexer.py
  </files>
  <action>
Fix E501 violations across 6 files. All structural wraps — no content changes.

**analyzers/quality.py line 80** — long `update_document_quality` call:
```python
self.db.update_document_quality(
    top_doc.id, top_doc.quality_score, is_gold_standard=True
)
```

**analyzers/shape.py line 178** — generator expression inside an f-string `{}` cannot be broken with parentheses (Python 3.12 allows it but readability is poor). Extract to a local variable before the f-string:
```python
depth_rows = chr(10).join(
    f"| H{level} | {count} |"
    for level, count in sorted(report.heading_depth_distribution.items())
)
```
Then replace the f-string expression `{chr(10).join(...)}` with `{depth_rows}`. Read the actual context around line 178 to identify the exact f-string and extract accurately.

**utils/ui.py lines 10, 35, 40** — three long lines. Read each line and wrap by parenthesising the expression or breaking at a natural boundary (function call arguments, string concatenation, conditional).

**search/engine.py line 114** — one long line. Read the line and wrap using the same parenthesis pattern.

**ingest/chunker.py line 252** — one long line. Wrap using parentheses.

**ingest/indexer.py line 97** — one long line. Wrap using parentheses.

After all edits, run checks:
```bash
uv run ruff check src/corpus_analyzer/analyzers/ src/corpus_analyzer/utils/ src/corpus_analyzer/search/ src/corpus_analyzer/ingest/
uv run pytest -v --tb=short -q 2>&1 | tail -5
```
  </action>
  <verify>
    <automated>uv run ruff check src/corpus_analyzer/analyzers/ src/corpus_analyzer/utils/ src/corpus_analyzer/search/ src/corpus_analyzer/ingest/ && uv run pytest -v --tb=short -q 2>&1 | tail -5</automated>
    <manual>Confirm zero E501 violations across all 6 files. Confirm full suite 281 passed.</manual>
  </verify>
  <done>Zero ruff violations in all 6 files; pytest 281 passed.</done>
</task>

</tasks>

<verification>
After both tasks, run:
```bash
uv run ruff check src/corpus_analyzer/classifiers/ src/corpus_analyzer/analyzers/ src/corpus_analyzer/utils/ src/corpus_analyzer/search/ src/corpus_analyzer/ingest/ --output-format=concise
uv run pytest -v --tb=short -q 2>&1 | tail -5
```
Expected: zero violations in all covered directories; 281 passed.
</verification>

<success_criteria>
- `uv run ruff check src/corpus_analyzer/classifiers/ src/corpus_analyzer/analyzers/ src/corpus_analyzer/utils/ src/corpus_analyzer/search/ src/corpus_analyzer/ingest/` exits 0
- `uv run pytest -v -q --tb=short` reports 281 passed, 0 failed
- No regex pattern content has been modified — only function call structure wrapped
- analyzers/shape.py uses `depth_rows` local variable to avoid f-string expression length issue
</success_criteria>

<output>
After completion, create `.planning/phases/10-manual-ruff-leaf-to-hub/10-02-SUMMARY.md` with:
- Files changed and exact line numbers for each wrap
- Any non-obvious wrapping decisions (especially shape.py extraction)
- Test result confirming 281 passed
</output>
