# PATCH-005: Fix known_files + group_by_file Schema Validation Error

## Group: B (High) — Search & Response Fixes
## Depends on: PATCH-004

## Objective

Fix the server-side schema validation error triggered when `known_files` is used with
`group_by_file`. The error message is: `"data/file_groups/0 must have required property 'matches'"`.
This is the schema validation layer rejecting the response because `SearchResultGroup.matches`
is empty and gets skipped by `#[serde(skip_serializing_if)]`, making the `matches` field
absent from the JSON — violating the JSON Schema which marks it as required.

## Severity: HIGH — production bug that breaks the recommended token-saving pattern

## Background

After PATCH-004, `SearchResultGroup` has `total_matches` and the `skip_serializing_if`
still applies. The JSON Schema generated by `schemars` marks `matches` as a required field
because it lacks `#[schemars(skip)]`. When all matches in a group are for known files,
`matches` is an empty vec and gets omitted from JSON. The MCP framework's schema validation
then rejects the response.

The fix: make `matches` and `known_matches` optional in the schema so they can be absent.

## Scope

| # | File | Action |
|---|------|--------|
| 1 | `crates/pathfinder/src/server/types.rs` | Add `#[schemars(skip)]` or make schema-optional |

## Step 1: Fix the schema for SearchResultGroup

**File:** `crates/pathfinder/src/server/types.rs`

**Find:**
```rust
    /// Full matches (for files NOT in `known_files`).
    ///
    /// Per-match objects contain only `{ line, column, content, context_before,
    /// context_after, enclosing_semantic_path }` — `file` and `version_hash` are
    /// deduplicated at group level to avoid repeating them for every match.
    #[serde(skip_serializing_if = "Vec::is_empty")]
    pub matches: Vec<GroupedMatch>,
    /// Minimal matches (for files in `known_files`).
    #[serde(skip_serializing_if = "Vec::is_empty")]
    pub known_matches: Vec<GroupedKnownMatch>,
```

**Replace with:**
```rust
    /// Full matches (for files NOT in `known_files`).
    ///
    /// Per-match objects contain only `{ line, column, content, context_before,
    /// context_after, enclosing_semantic_path }` — `file` and `version_hash` are
    /// deduplicated at group level to avoid repeating them for every match.
    ///
    /// Absent (not just empty) when all matches in this group are for known files.
    /// Check `total_matches` for the match count regardless of which array is populated.
    #[serde(skip_serializing_if = "Vec::is_empty")]
    #[schemars(skip)]
    pub matches: Vec<GroupedMatch>,
    /// Minimal matches (for files in `known_files`).
    ///
    /// Absent when no matches in this group are for known files.
    #[serde(skip_serializing_if = "Vec::is_empty")]
    #[schemars(skip)]
    pub known_matches: Vec<GroupedKnownMatch>,
```

By using `#[schemars(skip)]`, these fields are excluded from the JSON Schema entirely,
so schema validation won't require them. The `total_matches` field (added in PATCH-004)
is always present and gives agents the match count.

## Verification

```bash
# 1. Confirm schemars(skip) on both fields
grep -B1 'matches: Vec<GroupedMatch>' crates/pathfinder/src/server/types.rs
grep -B1 'known_matches: Vec<GroupedKnownMatch>' crates/pathfinder/src/server/types.rs

# Expected: both should show #[schemars(skip)] above

# 2. Run tests
cargo test -p pathfinder search

# 3. Full verification
cargo fmt --check
cargo clippy --all-targets --all-features -- -D warnings
cargo test --all
```
