# P3 Deferred Fixes Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Fix 5 remaining P3 deferred items (PERF-11 already done). Macros, test fixtures, and two llm.rs perf fixes.

**Architecture:** 3 independent tasks. EX-6/EX-7 are coupled (same macro system). CQ-13 is independent. PERF-13/16 are same file.

**Tech Stack:** Rust macros, #[cfg(test)]

---

### Task 1: define_patterns! macro + capture_name generation (EX-6/EX-7)

**Files:**
- Modify: `src/structural.rs` — replace Pattern enum + FromStr + Display + all_names with macro
- Modify: `src/language/mod.rs` — extend define_chunk_types! with optional `capture` field
- Modify: `src/parser/types.rs` — delete hand-written capture_name_to_chunk_type, re-export macro-generated version

**EX-6: define_patterns! macro**

Read `define_chunk_types!` in `src/language/mod.rs:307-377` as the template. Create `define_patterns!` in `src/structural.rs` that generates:
- Enum definition with `#[derive(Debug, Clone, Copy, PartialEq, Eq)]`
- `Display` impl (variant → lowercase string)
- `FromStr` impl (string → variant, with optional aliases)
- `all_names()` → `&[&str]`

The macro invocation:
```rust
define_patterns! {
    Builder => "builder";
    ErrorSwallow => "error_swallow", aliases = ["error-swallow"];
    Async => "async";
    Mutex => "mutex";
    Unsafe => "unsafe";
    Recursion => "recursion";
}
```

The `aliases` field handles the `error-swallow` → `ErrorSwallow` mapping in FromStr.

The `matches()` dispatch (line ~83) stays hand-written.

**EX-7: capture field in define_chunk_types!**

Extend the macro to accept optional `capture = "name"` per variant:
```rust
Constant => "constant", capture = "const";
Function => "function";  // capture defaults to "function"
```

Generate `pub fn capture_name_to_chunk_type(name: &str) -> Option<ChunkType>` from the capture fields. Variants without explicit `capture` use their display name. Delete the hand-written function in `parser/types.rs`.

Commit: `refactor: define_patterns! macro + capture_name generation from define_chunk_types! (EX-6/EX-7)`

---

### Task 2: Shared test fixtures (CQ-13)

**Files:**
- Create: `src/test_helpers.rs` (new file, `#[cfg(test)]` gated)
- Modify: `src/lib.rs` — add `#[cfg(test)] mod test_helpers;`
- Modify 8 files with `setup_store()`: `src/store/chunks.rs`, `src/store/mod.rs`, `src/store/calls.rs`, `src/store/notes.rs`, `src/store/types.rs`, `src/search.rs`, `src/related.rs`, `src/lib.rs`
- Modify 4 files with `mock_embedding()`: `src/store/chunks.rs`, `src/search.rs`, `src/related.rs`, `src/health.rs`

**Create `src/test_helpers.rs`:**

```rust
//! Shared test fixtures for cqs unit tests.
//!
//! Eliminates copy-pasted setup_store/mock_embedding/make_chunk across 8+ modules.

use crate::embedder::Embedding;
use crate::store::helpers::ModelInfo;
use crate::Store;
use tempfile::TempDir;

/// Create a temporary Store for testing. Returns (Store, TempDir).
/// TempDir must be kept alive for the Store's lifetime.
pub fn setup_store() -> (Store, TempDir) {
    let dir = TempDir::new().unwrap();
    let db_path = dir.path().join("index.db");
    let store = Store::open(&db_path).unwrap();
    store.init(&ModelInfo::default()).unwrap();
    (store, dir)
}

/// Create a deterministic 768-dim embedding from a seed value.
/// Normalized to unit length.
pub fn mock_embedding(seed: f32) -> Embedding {
    let mut data: Vec<f32> = (0..768).map(|i| (seed + i as f32 * 0.01).sin()).collect();
    let norm: f32 = data.iter().map(|x| x * x).sum::<f32>().sqrt();
    if norm > 0.0 {
        data.iter_mut().for_each(|x| *x /= norm);
    }
    Embedding::new(data)
}
```

Then replace all copies in the 8+ files with `use crate::test_helpers::{setup_store, mock_embedding};`.

For `make_chunk` — the signatures vary too much across files. Leave those as local helpers (they're customized per module). Only extract `setup_store` and `mock_embedding` which are truly identical.

Commit: `refactor(tests): extract shared test fixtures to test_helpers module (CQ-13)`

---

### Task 3: llm.rs performance fixes (PERF-13/PERF-16)

**Files:**
- Modify: `src/llm.rs`

**PERF-13: Truncate content before cloning**

In the collection loop (~line 474), replace:
```rust
cs.content.clone(),
```
With:
```rust
if cs.content.len() > MAX_CONTENT_CHARS {
    cs.content[..cs.content.floor_char_boundary(MAX_CONTENT_CHARS)].to_string()
} else {
    cs.content.clone()
},
```

**PERF-16: Hoist MODEL.to_string()**

In `submit_batch` (~line 157), allocate once before the iterator:
```rust
let model = MODEL.to_string();
```
Then inside the map: `model: model.clone(),`

In `resume_or_fetch_batch` (~line 366), same pattern:
```rust
let model = MODEL.to_string();
let api_summaries: Vec<(String, String, String)> = results
    .into_iter()
    .map(|(hash, summary)| (hash, summary, model.clone()))
    .collect();
```

Commit: `perf(llm): truncate before clone, hoist MODEL allocation (PERF-13/PERF-16)`

---

### Task Summary

| Task | Findings | Files | Can parallelize? |
|------|----------|-------|-----------------|
| 1 | EX-6/EX-7 | structural.rs, language/mod.rs, parser/types.rs | Yes |
| 2 | CQ-13 | test_helpers.rs (new), 8+ modules | Yes |
| 3 | PERF-13/16 | llm.rs | Yes |

All 3 are fully independent — different files, no overlap.

### Status of P3 deferred:
- ~~PERF-11~~: Already fixed (batch INSERT pattern already present)
- EX-6/EX-7: Task 1
- CQ-13: Task 2
- PERF-13/16: Task 3
