You are a senior code reviewer. Review the following code changes.

## Specification

No specification provided. Focus on correctness, tests, and integration.





## Code Changes

```diff
diff --git a/expert_build/cli.py b/expert_build/cli.py
index 0ac957f..37c0171 100644
--- a/expert_build/cli.py
+++ b/expert_build/cli.py
@@ -138,6 +138,19 @@ def main():
                        help="LLM timeout in seconds (default: 600)")
     drr_p.add_argument("--domain", help="Domain description for derive context")
 
+    # -- index-sources --
+    idx_p = sub.add_parser("index-sources", help="Build FTS5 chunks database from source documents")
+    idx_p.add_argument("--input-dir", default="sources", help="Source directory (default: sources)")
+    idx_p.add_argument("--recursive", "-r", action="store_true",
+                       help="Recursively search subdirectories")
+    idx_p.add_argument("--db", default="rag_fts.db", help="Output database path (default: rag_fts.db)")
+    idx_p.add_argument("--type", default="source", choices=["source", "summary", "chunked-summary"],
+                       help="Chunk type metadata (default: source)")
+    idx_p.add_argument("--chunk-size", type=int, default=2000,
+                       help="Target chunk size in chars (default: 2000)")
+    idx_p.add_argument("--rebuild", action="store_true",
+                       help="Drop and rebuild the index from scratch")
+
     # -- status --
     sub.add_parser("status", help="Show pipeline progress")
 
@@ -162,6 +175,7 @@ def main():
         "accept-beliefs": lambda a: _lazy("propose", "cmd_accept_beliefs")(a),
         "cert-coverage": lambda a: _lazy("coverage", "cmd_cert_coverage")(a),
         "exam": lambda a: _lazy("exam", "cmd_exam")(a),
+        "index-sources": lambda a: _lazy("index_sources", "cmd_index_sources")(a),
         "pipeline": lambda a: _lazy("pipeline", "cmd_pipeline")(a),
         "derive-review-repair": lambda a: _lazy("pipeline", "cmd_derive_review_repair")(a),
         "status": lambda a: _lazy("init_cmd", "cmd_status")(a),
diff --git a/expert_build/index_sources.py b/expert_build/index_sources.py
new file mode 100644
index 0000000..1b3dcb6
--- /dev/null
+++ b/expert_build/index_sources.py
@@ -0,0 +1,119 @@
+"""Build FTS5 chunks database from source documents."""
+
+import sqlite3
+import sys
+from pathlib import Path
+
+from .chunk_docs import chunk_markdown, chunk_python, chunk_fixed, _strip_frontmatter
+
+
+DEFAULT_DB = "rag_fts.db"
+DEFAULT_CHUNK_SIZE = 2000
+
+
+def _init_db(db_path, rebuild=False):
+    """Create the chunks and FTS5 tables."""
+    conn = sqlite3.connect(db_path)
+    if rebuild:
+        conn.execute("DROP TABLE IF EXISTS chunks_fts")
+        conn.execute("DROP TABLE IF EXISTS chunks")
+
+    conn.execute("""
+        CREATE TABLE IF NOT EXISTS chunks (
+            id INTEGER PRIMARY KEY AUTOINCREMENT,
+            text TEXT NOT NULL,
+            cluster TEXT DEFAULT '',
+            filename TEXT NOT NULL,
+            section TEXT DEFAULT '',
+            chunk_type TEXT DEFAULT 'source',
+            source_url TEXT DEFAULT ''
+        )
+    """)
+    conn.execute("""
+        CREATE VIRTUAL TABLE IF NOT EXISTS chunks_fts
+        USING fts5(text, content=chunks, content_rowid=id,
+                   tokenize="porter unicode61")
+    """)
+    conn.commit()
+    return conn
+
+
+def _insert_chunks(conn, chunks, filename, chunk_type="source", source_url=""):
+    """Insert chunks into the database."""
+    for i, chunk_text in enumerate(chunks):
+        section = f"chunk {i + 1}/{len(chunks)}" if len(chunks) > 1 else ""
+        conn.execute(
+            "INSERT INTO chunks (text, filename, section, chunk_type, source_url) "
+            "VALUES (?, ?, ?, ?, ?)",
+            (chunk_text.strip(), str(filename), section, chunk_type, source_url),
+        )
+
+
+def cmd_index_sources(args):
+    """Build FTS5 chunks database from source documents."""
+    input_dir = Path(args.input_dir)
+    if not input_dir.exists():
+        print(f"Source directory not found: {input_dir}")
+        sys.exit(1)
+
+    db_path = args.db
+    rebuild = args.rebuild
+    chunk_type = args.type
+    max_chars = args.chunk_size
+
+    glob = input_dir.rglob if getattr(args, "recursive", False) else input_dir.glob
+    sources = sorted(
+        [*glob("*.md"), *glob("*.py"), *glob("*.txt")],
+        key=lambda p: p.name,
+    )
+    if not sources:
+        print(f"No .md, .py, or .txt files in {input_dir}")
+        return
+
+    print(f"Indexing {len(sources)} files into {db_path}")
+    conn = _init_db(db_path, rebuild=rebuild)
+
+    try:
+        existing = set()
+        if not rebuild:
+            cur = conn.execute("SELECT DISTINCT filename FROM chunks")
+            existing = {row[0] for row in cur.fetchall()}
+
+        indexed = 0
+        skipped = 0
+
+        for source_path in sources:
+            if str(source_path) in existing:
+                skipped += 1
+                continue
+
+            raw = source_path.read_text()
+            meta, content = _strip_frontmatter(raw)
+
+            if not content.strip():
+                continue
+
+            source_url = meta.get("source_url") or meta.get("source", "")
+            if source_url and not source_url.startswith(("http://", "https://")):
+                source_url = ""
+
+            if source_path.suffix == ".py":
+                chunks = chunk_python(content, max_chars=max_chars)
+            elif source_path.suffix == ".md":
+                chunks = chunk_markdown(content, max_chars=max_chars)
+            else:
+                chunks = chunk_fixed(content, max_chars=max_chars)
+
+            _insert_chunks(conn, chunks, source_path, chunk_type=chunk_type,
+                           source_url=source_url)
+            indexed += 1
+            print(f"  {source_path.name} -> {len(chunks)} chunk(s)")
+
+        if indexed:
+            conn.commit()
+            conn.execute("INSERT INTO chunks_fts(chunks_fts) VALUES('rebuild')")
+            conn.commit()
+
+        print(f"\nIndexed {indexed} files ({skipped} already indexed)")
+    finally:
+        conn.close()
diff --git a/tests/test_index_sources.py b/tests/test_index_sources.py
new file mode 100644
index 0000000..df9b500
--- /dev/null
+++ b/tests/test_index_sources.py
@@ -0,0 +1,208 @@
+"""Tests for expert_build.index_sources — FTS5 chunk indexing."""
+
+import sqlite3
+import types
+from pathlib import Path
+
+import pytest
+
+from expert_build.index_sources import cmd_index_sources, _init_db, _insert_chunks
+
+
+@pytest.fixture
+def source_dir(tmp_path):
+    d = tmp_path / "sources"
+    d.mkdir()
+    return d
+
+
+@pytest.fixture
+def work_dir(tmp_path, monkeypatch):
+    wd = tmp_path / "work"
+    wd.mkdir()
+    monkeypatch.chdir(wd)
+    return wd
+
+
+def make_args(input_dir, db="rag_fts.db", rebuild=False, recursive=False,
+              chunk_type="source", chunk_size=2000):
+    return types.SimpleNamespace(
+        input_dir=str(input_dir),
+        db=db,
+        rebuild=rebuild,
+        recursive=recursive,
+        type=chunk_type,
+        chunk_size=chunk_size,
+    )
+
+
+# --- _init_db ---
+
+def test_init_db_creates_tables(tmp_path):
+    db_path = str(tmp_path / "test.db")
+    conn = _init_db(db_path)
+    cur = conn.execute("SELECT name FROM sqlite_master WHERE type='table'")
+    tables = {row[0] for row in cur.fetchall()}
+    assert "chunks" in tables
+    assert "chunks_fts" in tables
+    conn.close()
+
+
+def test_init_db_rebuild_clears_data(tmp_path):
+    db_path = str(tmp_path / "test.db")
+    conn = _init_db(db_path)
+    _insert_chunks(conn, ["test content"], "test.md")
+    conn.commit()
+    conn.close()
+
+    conn = _init_db(db_path, rebuild=True)
+    cur = conn.execute("SELECT COUNT(*) FROM chunks")
+    assert cur.fetchone()[0] == 0
+    conn.close()
+
+
+# --- cmd_index_sources ---
+
+def test_indexes_markdown_files(source_dir, work_dir):
+    (source_dir / "doc.md").write_text("# Hello\nSome content about testing.")
+    db_path = str(work_dir / "test.db")
+    args = make_args(source_dir, db=db_path)
+    cmd_index_sources(args)
+
+    conn = sqlite3.connect(db_path)
+    cur = conn.execute("SELECT COUNT(*) FROM chunks")
+    assert cur.fetchone()[0] >= 1
+    cur = conn.execute("SELECT filename FROM chunks")
+    filenames = [row[0] for row in cur.fetchall()]
+    assert any("doc.md" in f for f in filenames)
+    conn.close()
+
+
+def test_indexes_python_files(source_dir, work_dir):
+    (source_dir / "module.py").write_text("import os\n\ndef hello():\n    pass\n")
+    db_path = str(work_dir / "test.db")
+    args = make_args(source_dir, db=db_path)
+    cmd_index_sources(args)
+
+    conn = sqlite3.connect(db_path)
+    cur = conn.execute("SELECT COUNT(*) FROM chunks")
+    assert cur.fetchone()[0] >= 1
+    conn.close()
+
+
+def test_skips_already_indexed(source_dir, work_dir, capsys):
+    (source_dir / "doc.md").write_text("# Hello\nContent")
+    db_path = str(work_dir / "test.db")
+    args = make_args(source_dir, db=db_path)
+
+    cmd_index_sources(args)
+    cmd_index_sources(args)
+
+    captured = capsys.readouterr()
+    assert "already indexed" in captured.out
+
+
+def test_rebuild_reindexes(source_dir, work_dir):
+    (source_dir / "doc.md").write_text("# Hello\nContent")
+    db_path = str(work_dir / "test.db")
+
+    args = make_args(source_dir, db=db_path)
+    cmd_index_sources(args)
+
+    args = make_args(source_dir, db=db_path, rebuild=True)
+    cmd_index_sources(args)
+
+    conn = sqlite3.connect(db_path)
+    cur = conn.execute("SELECT COUNT(*) FROM chunks")
+    assert cur.fetchone()[0] >= 1
+    conn.close()
+
+
+def test_recursive_indexes_nested(source_dir, work_dir):
+    subdir = source_dir / "sub"
+    subdir.mkdir()
+    (subdir / "nested.md").write_text("# Nested\nContent here")
+    (source_dir / "top.md").write_text("# Top\nContent here")
+    db_path = str(work_dir / "test.db")
+    args = make_args(source_dir, db=db_path, recursive=True)
+    cmd_index_sources(args)
+
+    conn = sqlite3.connect(db_path)
+    cur = conn.execute("SELECT COUNT(DISTINCT filename) FROM chunks")
+    assert cur.fetchone()[0] == 2
+    conn.close()
+
+
+def test_non_recursive_skips_nested(source_dir, work_dir):
+    subdir = source_dir / "sub"
+    subdir.mkdir()
+    (subdir / "nested.md").write_text("# Nested\nContent here")
+    (source_dir / "top.md").write_text("# Top\nContent here")
+    db_path = str(work_dir / "test.db")
+    args = make_args(source_dir, db=db_path, recursive=False)
+    cmd_index_sources(args)
+
+    conn = sqlite3.connect(db_path)
+    cur = conn.execute("SELECT COUNT(DISTINCT filename) FROM chunks")
+    assert cur.fetchone()[0] == 1
+    conn.close()
+
+
+def test_chunk_type_stored(source_dir, work_dir):
+    (source_dir / "doc.md").write_text("# Hello\nContent")
+    db_path = str(work_dir / "test.db")
+    args = make_args(source_dir, db=db_path, chunk_type="summary")
+    cmd_index_sources(args)
+
+    conn = sqlite3.connect(db_path)
+    cur = conn.execute("SELECT chunk_type FROM chunks")
+    types = [row[0] for row in cur.fetchall()]
+    assert all(t == "summary" for t in types)
+    conn.close()
+
+
+def test_fts5_search_works(source_dir, work_dir):
+    (source_dir / "doc.md").write_text("# Kubernetes\nPod scheduling and node affinity rules.")
+    db_path = str(work_dir / "test.db")
+    args = make_args(source_dir, db=db_path)
+    cmd_index_sources(args)
+
+    conn = sqlite3.connect(db_path)
+    conn.row_factory = sqlite3.Row
+    cur = conn.execute("""
+        SELECT c.text, c.filename
+        FROM chunks_fts
+        JOIN chunks c ON c.id = chunks_fts.rowid
+        WHERE chunks_fts MATCH 'kubernetes'
+    """)
+    results = [dict(row) for row in cur.fetchall()]
+    assert len(results) >= 1
+    assert "Kubernetes" in results[0]["text"]
+    conn.close()
+
+
+def test_large_file_chunked(source_dir, work_dir):
+    text = "# Section 1\n" + "x" * 3000 + "\n\n# Section 2\n" + "y" * 3000
+    (source_dir / "big.md").write_text(text)
+    db_path = str(work_dir / "test.db")
+    args = make_args(source_dir, db=db_path, chunk_size=2000)
+    cmd_index_sources(args)
+
+    conn = sqlite3.connect(db_path)
+    cur = conn.execute("SELECT COUNT(*) FROM chunks")
+    assert cur.fetchone()[0] == 2
+    conn.close()
+
+
+def test_source_url_from_frontmatter(source_dir, work_dir):
+    fm = "---\nsource_url: https://example.com/doc\n---\n\n# Hello\nContent"
+    (source_dir / "doc.md").write_text(fm)
+    db_path = str(work_dir / "test.db")
+    args = make_args(source_dir, db=db_path)
+    cmd_index_sources(args)
+
+    conn = sqlite3.connect(db_path)
+    cur = conn.execute("SELECT source_url FROM chunks")
+    urls = [row[0] for row in cur.fetchall()]
+    assert all(u == "https://example.com/doc" for u in urls)
+    conn.close()

```

## Observation Results

You previously requested observations. Here are the results:

```json
{
  "chunk_markdown_body": {
    "function": "chunk_markdown",
    "file": "expert_build/chunk_docs.py",
    "start_line": 9,
    "end_line": 28,
    "source": "def chunk_markdown(text, max_chars=25000):\n    \"\"\"Split markdown by heading boundaries, merging small sections.\"\"\"\n    parts = re.split(r\"(?=^#{1,2} )\", text, flags=re.MULTILINE)\n    parts = [p for p in parts if p.strip()]\n\n    if len(parts) <= 1:\n        return chunk_fixed(text, max_chars)\n\n    chunks = []\n    current = \"\"\n    for part in parts:\n        if len(current) + len(part) > max_chars and current:\n            chunks.append(current)\n            current = part\n        else:\n            current += part\n    if current:\n        chunks.append(current)\n\n    return chunks"
  },
  "chunk_python_body": {
    "function": "chunk_python",
    "file": "expert_build/chunk_docs.py",
    "start_line": 31,
    "end_line": 75,
    "source": "def chunk_python(text, max_chars=25000):\n    \"\"\"Split Python by top-level class/def boundaries, keeping imports.\"\"\"\n    lines = text.split(\"\\n\")\n\n    preamble_end = 0\n    for i, line in enumerate(lines):\n        if re.match(r\"^(class |def )\", line) or (line.startswith(\"@\") and i + 1 < len(lines) and re.match(r\"^(class |def |@)\", lines[i + 1])):\n            preamble_end = i\n            break\n    else:\n        return chunk_fixed(text, max_chars)\n\n    preamble = \"\\n\".join(lines[:preamble_end]).rstrip() + \"\\n\\n\"\n\n    boundaries = []\n    for i, line in enumerate(lines[preamble_end:], start=preamble_end):\n        if re.match(r\"^(class |def )\", line):\n            # Include preceding decorator lines\n            start = i\n            while start > preamble_end and lines[start - 1].startswith(\"@\"):\n                start -= 1\n            if not boundaries or boundaries[-1] != start:\n                boundaries.append(start)\n\n    if not boundaries:\n        return chunk_fixed(text, max_chars)\n\n    sections = []\n    for j, start in enumerate(boundaries):\n        end = boundaries[j + 1] if j + 1 < len(boundaries) else len(lines)\n        section = \"\\n\".join(lines[start:end])\n        sections.append(section)\n\n    chunks = []\n    current = preamble\n    for section in sections:\n        if len(current) + len(section) > max_chars and current.strip() != preamble.strip():\n            chunks.append(current)\n            current = preamble + section\n        else:\n            current += section + \"\\n\"\n    if current.strip():\n        chunks.append(current)\n\n    return chunks"
  },
  "chunk_fixed_body": {
    "function": "chunk_fixed",
    "file": "expert_build/chunk_docs.py",
    "start_line": 78,
    "end_line": 92,
    "source": "def chunk_fixed(text, max_chars=25000, overlap=500):\n    \"\"\"Split text into fixed-size windows with overlap.\"\"\"\n    if len(text) <= max_chars:\n        return [text]\n\n    overlap = min(overlap, max_chars // 4)\n    step = max(max_chars - overlap, 1)\n\n    chunks = []\n    start = 0\n    while start < len(text):\n        end = start + max_chars\n        chunks.append(text[start:end])\n        start += step\n    return chunks"
  },
  "strip_frontmatter_body": {
    "function": "_strip_frontmatter",
    "file": "expert_build/chunk_docs.py",
    "start_line": 95,
    "end_line": 107,
    "source": "def _strip_frontmatter(text):\n    \"\"\"Strip YAML frontmatter and return (frontmatter_dict, content).\"\"\"\n    meta = {}\n    content = text\n    if text.startswith(\"---\"):\n        end = text.find(\"---\", 3)\n        if end != -1:\n            for line in text[3:end].splitlines():\n                if \":\" in line:\n                    key, _, val = line.partition(\":\")\n                    meta[key.strip()] = val.strip()\n            content = text[end + 3:].strip()\n    return meta, content"
  },
  "lazy_body": {
    "function": "_lazy",
    "file": "expert_build/cli.py",
    "start_line": 11,
    "end_line": 14,
    "source": "def _lazy(module_name, func_name):\n    \"\"\"Lazy import to keep startup fast.\"\"\"\n    mod = importlib.import_module(f\".{module_name}\", package=\"expert_build\")\n    return getattr(mod, func_name)"
  },
  "chunk_docs_imports": {
    "file": "expert_build/chunk_docs.py",
    "imports": [
      "re",
      "sys"
    ],
    "from_imports": [
      {
        "module": "datetime",
        "names": [
          "date"
        ]
      },
      {
        "module": "pathlib",
        "names": [
          "Path"
        ]
      }
    ],
    "import_section": "\"\"\"Chunk large documents into entry-sized pieces.\"\"\"\n\nimport re\nimport sys\nfrom datetime import date\nfrom pathlib import Path\n\n\ndef chunk_markdown(text, max_chars=25000):\n    \"\"\"Split markdown by heading boundaries, merging small sections.\"\"\""
  },
  "strip_frontmatter_usages": {
    "symbol": "_strip_frontmatter",
    "usages": [
      {
        "file": "expert_build/chunk_docs.py",
        "line": 95,
        "text": "def _strip_frontmatter(text):"
      },
      {
        "file": "expert_build/chunk_docs.py",
        "line": 142,
        "text": "meta, content = _strip_frontmatter(raw)"
      },
      {
        "file": "expert_build/index_sources.py",
        "line": 7,
        "text": "from .chunk_docs import chunk_markdown, chunk_python, chunk_fixed, _strip_frontmatter"
      },
      {
        "file": "expert_build/index_sources.py",
        "line": 91,
        "text": "meta, content = _strip_frontmatter(raw)"
      }
    ],
    "production_usages": [
      {
        "file": "expert_build/chunk_docs.py",
        "line": 95,
        "text": "def _strip_frontmatter(text):"
      },
      {
        "file": "expert_build/chunk_docs.py",
        "line": 142,
        "text": "meta, content = _strip_frontmatter(raw)"
      },
      {
        "file": "expert_build/index_sources.py",
        "line": 7,
        "text": "from .chunk_docs import chunk_markdown, chunk_python, chunk_fixed, _strip_frontmatter"
      },
      {
        "file": "expert_build/index_sources.py",
        "line": 91,
        "text": "meta, content = _strip_frontmatter(raw)"
      }
    ],
    "test_usages": [],
    "production_count": 4,
    "test_count": 0,
    "total_count": 4
  },
  "project_deps": {
    "repo": ".",
    "pyproject_toml": "[project]\nname = \"expert-agent-builder\"\nversion = \"0.2.0\"\ndescription = \"Build expert agents from documented domains\"\nreadme = \"README.md\"\nrequires-python = \">=3.12\"\nlicense = \"MIT\"\ndependencies = [\"httpx>=0.27\", \"beautifulsoup4>=4.12\", \"ftl-reasons\", \"pypdf>=4.0\"]\n\n[project.optional-dependencies]\nembeddings = [\"fastembed>=0.7.4\", \"numpy\"]\n\n[project.scripts]\nexpert-build = \"expert_build.cli:main\"\n\n[tool.uv.sources]\nftl-reasons = { path = \"../ftl-reasons\", editable = true }\n\n[build-system]\nrequires = [\"hatchling\"]\nbuild-backend = \"hatchling.build\"\n\n[tool.hatch.build.targets.wheel]\npackages = [\"expert_build\"]\n",
    "dependencies": [
      "httpx>=0.27",
      "beautifulsoup4>=4.12",
      "ftl-reasons",
      "pypdf>=4.0"
    ],
    "optional_dependencies": {
      "embeddings": [
        "fastembed>=0.7.4",
        "numpy"
      ]
    }
  }
}
```

Use these results to inform your review. Do not request the same observations again.


## Instructions

For each significant change (new file, modified function, etc.), provide a structured verdict.

Use this exact format for each change:

### <file_path or file_path:function_name>
VERDICT: PASS | CONCERN | BLOCK
CORRECTNESS: VALID | QUESTIONABLE | BROKEN
SPEC_COMPLIANCE: MEETS | PARTIAL | VIOLATES | N/A
ISSUE_COMPLIANCE: ADDRESSES | PARTIAL | UNRELATED | N/A
BELIEF_COMPLIANCE: CONSISTENT | VIOLATES | N/A
TEST_COVERAGE: COVERED | PARTIAL | UNTESTED
INTEGRATION: WIRED | PARTIAL | MISSING
REASONING: <brief explanation of your assessment>
---

## Review Criteria

1. **CORRECTNESS**: Does the code do what it claims? Is the logic sound?
   - VALID: Logic is correct, no bugs apparent
   - QUESTIONABLE: Logic may have edge cases or unclear behavior
   - BROKEN: Clear bugs or incorrect behavior

2. **SPEC_COMPLIANCE**: Does it meet MUST requirements from the spec?
   - MEETS: All relevant spec requirements satisfied
   - PARTIAL: Some requirements met, others missing or incomplete
   - VIOLATES: Contradicts spec requirements
   - N/A: No spec provided or not applicable

3. **ISSUE_COMPLIANCE** (only when an issue is provided): Do the changes address the problem or feature described in the issue?
   - ADDRESSES: Changes directly solve the issue's stated problem or implement the requested feature
   - PARTIAL: Changes partially address the issue but leave some aspects unresolved
   - UNRELATED: Changes do not appear related to the issue
   - N/A: No issue provided

4. **TEST_COVERAGE**: Are there tests for the new/changed code?
   - COVERED: Tests exist and cover the changes
   - PARTIAL: Some tests exist but coverage is incomplete
   - UNTESTED: No tests for the changes

5. **INTEGRATION**: Are callers updated? Is the feature usable end-to-end?
   - WIRED: Feature is fully integrated and usable
   - PARTIAL: Interface exists but callers not updated, or integration incomplete
   - MISSING: No integration with existing code

6. **BELIEF_COMPLIANCE** (only when beliefs are provided): Do the changes respect known architectural invariants, contracts, and rules?
   - CONSISTENT: Changes align with or reinforce known beliefs
   - VIOLATES: Changes contradict a specific belief — cite the belief ID
   - N/A: No beliefs provided or no relevant beliefs apply

## Verdict Guidelines

- **BLOCK**: Security issues, broken functionality, spec violations, or missing critical integration
- **CONCERN**: Missing tests, partial integration, questionable patterns, or unclear logic
- **PASS**: Correct, tested, well-integrated code

## Important

- Full function bodies for modified functions may be available in the observations section — use them to verify the complete logic, not just the diff hunks
- Related test files (prefixed with ``related_test:``) may be included in observations — check whether existing test assertions still match modified return types, signatures, or behavior. Flag any test that would break due to the changes
- If duplicate test coverage is detected (multiple test files covering the same source), note it in your review
- Focus on actual issues, not style preferences
- If a method signature is added but callers aren't updated, that's PARTIAL integration
- Be specific in reasoning - reference line numbers or function names
- When in doubt, use CONCERN rather than PASS

## Self-Review

After completing your review, add a brief self-assessment:

### SELF_REVIEW
LIMITATIONS: <what context were you missing that affected review quality?>
---

Examples of limitations:
- "Could not see full class to verify no other methods access the modified field"
- "Test file not included in diff - cannot verify coverage claims"
- "Spec file referenced but not provided"


## Feature Requests

If this review tool could be improved to help you do a better job, suggest features:

### FEATURE_REQUESTS
- <suggestion 1>
- <suggestion 2>
---

Examples:
- "Include full file context for modified functions, not just diff hunks"
- "Show callers of modified methods to verify integration"
- "Include test file alongside implementation changes"

Only include this section if you have specific suggestions. Skip if none.
