---
milestone: v2.1
name: Result Quality
audited: 2026-02-24T18:00:00Z
status: complete
scores:
  requirements: 13/13
  phases: 7/7
  integration: 8/8
  flows: 6/6
gaps:
  requirements: []
  integration: []
  flows: []
tech_debt:
  - phase: 18-cli-chunk-display
    items:
      - "Human verification pending: manual CLI spot-check with live LanceDB index required to confirm path:start-end format renders correctly for chunked results."
  - phase: milestone-wide
    items:
      - "QUAL-01 ID collision: Phase 16 (v1.5) SUMMARY claims QUAL-01 completion (ruff+mypy+320 tests), but v2.1 QUAL-01 is a different, stricter requirement (85%+ branch coverage). Resolve ID collision before Phase 25 planning."
---

# Milestone v2.1: Result Quality — Audit Report

**Milestone:** v2.1 Result Quality
**Scope:** Phases 19–25
**Audited:** 2026-02-24T18:00:00Z
**Status:** COMPLETE — all phases and requirements verified
**Requirements score:** 13/13 satisfied

---

## Executive Summary

Milestone v2.1 is complete. Phase 23 (normalisation + MCP sort), Phase 24 (JSON output), and Phase 25 (graph MCP + quality gate) are verified as shipped and aligned with roadmap, requirements, and state docs. Regression and quality checks pass with green test, lint, and mypy gates.

---

## Requirements Coverage (3-Source Cross-Reference)

| REQ-ID | Phase | VERIFICATION.md | SUMMARY Frontmatter | REQUIREMENTS.md | Final Status |
|--------|-------|-----------------|---------------------|-----------------|--------------|
| CHUNK-03 | 19 | passed (6/6) | `19-01` lists CHUNK-03 | `[x]` | **satisfied** |
| SUB-01 | 20 | passed (12/12) | `20-01` lists SUB-01 | `[x]` | **satisfied** |
| SUB-02 | 20 | passed (12/12) | `20-02` lists SUB-02 | `[x]` | **satisfied** |
| SUB-03 | 21 | passed (6/6) | `21-01` lists SUB-03 | `[x]` | **satisfied** |
| NAME-01 | 22 | passed (8/8) | `22-01` + `22-02` list NAME-01 | `[x]` | **satisfied** |
| NAME-02 | 22 | passed (8/8) | `22-02` lists NAME-02 | `[x]` | **satisfied** |
| NAME-03 | 22 | passed (8/8) | `22-01` + `22-02` list NAME-03 | `[x]` | **satisfied** |
| NORM-01 | 23 | passed | `23-01` summary present | `[x]` | **satisfied** |
| SORT-01 | 23 | passed | `23-02` summary present | `[x]` | **satisfied** |
| JSON-01 | 24 | passed | phase plan + tests present | `[x]` | **satisfied** |
| QUAL-01 | 25 | passed | coverage evidence captured | `[x]` | **satisfied** |
| QUAL-02 | 25 | passed | hallucination tests present | `[x]` | **satisfied** |
| GRAPH-01 | 25 | passed | graph MCP tests present | `[x]` | **satisfied** |

**Orphan check:** All 13 requirements in REQUIREMENTS.md traceability table are mapped to phases 19-25. No orphaned requirements.

---

## Phase Verification Summary

| Phase | Status | Score | Critical Gaps |
|-------|--------|-------|---------------|
| Phase 19: MCP Chunk Response | PASSED | 6/6 | None |
| Phase 20: Python Method Sub-Chunking | PASSED | 12/12 | None |
| Phase 21: TypeScript Method Sub-Chunking | PASSED | 6/6 | None |
| Phase 22: Name Filtering | PASSED | 8/8 | None |
| Phase 23: Score Normalisation + MCP Sort | PASSED | 2/2 | None |
| Phase 24: JSON Output | PASSED | 1/1 | None |
| Phase 25: Graph MCP + Quality Gate | PASSED | 3/3 | None |

### Phase 19 Highlights

- MCP `corpus_search` returns `text` (full chunk body), `start_line`, `end_line`, `chunk_name` per result
- Removed snippet truncation and `Path.read_text()` file reads from MCP hot path
- Legacy rows return `text=""` safely; 7 regression-guard tests assert `snippet`/`full_content` absent
- 345 tests pass, ruff/mypy clean

### Phase 20 Highlights

- `_chunk_class()` produces header chunk (`ClassName`) + per-method chunks (`ClassName.method_name`)
- All method types handled: `__init__`, dunders, `@property`, `@classmethod`, `@staticmethod`, `async def`
- 24 new tests (10 SUB-01 + 14 SUB-02); 369 tests pass, ruff/mypy clean

### Phase 21 Highlights

- `_chunk_ts_class()` produces header chunk + one `ClassName.method_name` chunk per `method_definition`/`abstract_method_signature`
- Constructor becomes `ClassName.constructor`; abstract method declarations produce individual chunks
- Top-level functions, interfaces, enums unaffected; monolithic class chunk fully replaced
- 13 new tests (SUB-03); 382 tests pass, ruff/mypy clean

### Phase 22 Highlights

- `hybrid_search(name=...)` post-filters on `chunk_name` column (case-insensitive substring)
- CLI `--name/-N` option wired; `query` made Optional; name-only mode via `effective_query=""`
- MCP `corpus_search` accepts `name: Optional[str]`, forwards to engine
- Empty-query table-scan path for name-only search (skips text-term filter)
- 16 new tests (7 engine + 6 CLI + 3 MCP); 398 tests pass, ruff/mypy clean

---

## Cross-Phase Integration

All wiring verified by integration checker. 8 wiring points confirmed, 0 broken, 6 E2E flows complete.

### CHUNK-03 Wiring (Phase 17→18→19) — WIRED

```
chunk_file() → indexer.py:457 chunk.get("chunk_text","")
  → LanceDB chunk_text column (schema v4)
  → server.py:108 row.get("chunk_text","")
  → MCP response text field (content-first ordering)
```

Legacy rows pre-Phase 17 return `text=""`. No breaks.

### SUB-01/02 Wiring (Phase 20→17→19) — WIRED

```
_chunk_class() header: chunk_name=node.name, chunk_text=header_text
_chunk_class() methods: chunk_name=f"{node.name}.{method.name}", chunk_text=method_text
  → chunk_python():332 extends result list
  → chunk_file() dispatch → _enforce_char_limit() (preserves chunk_name/chunk_text)
  → indexer.py:456-457 chunk.get("chunk_name","") / chunk.get("chunk_text","")
  → LanceDB chunk_name + chunk_text columns
  → server.py:112-114 row.get("chunk_name","") / row.get("chunk_text","")
  → MCP response chunk_name + text fields
```

### SUB-03 Wiring (Phase 21→17→19) — WIRED

```
_chunk_ts_class() header: chunk_name=class_name, chunk_text=header_text
_chunk_ts_class() methods: chunk_name=f"{class_name}.{method_name}", chunk_text=method_text
  → chunk_typescript():279-283 detects class nodes, calls _chunk_ts_class(), extends output
  → chunk_file() dispatch (chunker.py:509-512) → _enforce_char_limit() (chunk_name/chunk_text preserved)
  → indexer.py:456-457 safe get with empty-string defaults
  → LanceDB chunk_name + chunk_text columns
  → server.py:112-113 row.get("chunk_text","") / row.get("chunk_name","")
  → MCP response text + chunk_name fields
```

Note: `chunk_lines()` fallback paths produce no `chunk_name`/`chunk_text` — handled safely by `chunk.get("chunk_name","")` in indexer. Pre-existing behaviour.

### NAME-01/02/03 Wiring (Phase 22→Engine) — WIRED

```
CLI --name/-N → name_filter → hybrid_search(name=name_filter)  [NAME-01]
MCP name: Optional[str] → engine.hybrid_search(name=name)      [NAME-02]
CLI query=None + name_filter present → effective_query=""        [NAME-03]
  → engine.is_empty_query=True → table scan (not hybrid)
  → text-term filter skipped (query_terms empty set)
  → name filter: [r for r in results if name_lower in chunk_name.lower()]
```

### E2E Flows

| Flow | Status |
|------|--------|
| Index Python class → MCP returns ClassName header chunk | WIRED |
| Index Python class → MCP returns ClassName.method_name chunk | WIRED |
| MCP corpus_search returns self-contained result (no follow-up needed) | WIRED |
| Index TypeScript class → ClassName.method_name chunk searchable | WIRED |
| `corpus search --name foo` returns name-matching chunks (no query required) | WIRED |
| MCP `corpus_search(name="ClassName.method")` returns matching method chunk | WIRED |

### Integration Gap

No critical integration gaps remain for v2.1 delivery.

---

## Unsatisfied Requirements

None.

---

## Tech Debt

### Phase 18: CLI Chunk Display
- Manual CLI spot-check with live LanceDB index not yet performed (cannot be automated without real indexed data). Low severity — unit tests cover the format logic.

### Phases 20-21: Method Sub-Chunking
- No outstanding debt. Round-trip class fixtures were added and verified.

### Milestone-Wide
- **QUAL-01 ID collision:** Phase 16 (v1.5) SUMMARY claims `QUAL-01` completion (ruff+mypy+320 tests), but v2.1 QUAL-01 is a different, stricter requirement (85%+ branch coverage on chunking modules). Resolve before Phase 25 planning — rename one to avoid confusion.

---

*Audited: 2026-02-24T18:00:00Z*
*Verifier: Claude (gsd-audit-milestone)*
*Integration checker: claude-sonnet-4-6*
