---
phase: 01-foundation
plan: 01
type: tdd
wave: 1
depends_on: []
files_modified:
  - src/corpus_analyzer/store/__init__.py
  - src/corpus_analyzer/store/schema.py
  - tests/store/__init__.py
  - tests/store/test_schema.py
autonomous: true
requirements:
  - CONF-02
  - INGEST-04
  - INGEST-06

must_haves:
  truths:
    - "ChunkRecord LanceModel can be instantiated with all required fields"
    - "Vector(768) dimension is declared and validates correctly"
    - "chunk_id is a deterministic sha256 derived from source_name + file_path + start_line + text"
    - "All metadata fields (file_path, source_name, file_type, content_hash, embedding_model, indexed_at, start_line, end_line) are present and typed"
  artifacts:
    - path: "src/corpus_analyzer/store/schema.py"
      provides: "ChunkRecord LanceModel schema + make_chunk_id() helper"
      exports: ["ChunkRecord", "make_chunk_id"]
    - path: "tests/store/test_schema.py"
      provides: "Schema correctness tests"
  key_links:
    - from: "src/corpus_analyzer/store/schema.py"
      to: "lancedb.pydantic.LanceModel"
      via: "class ChunkRecord(LanceModel)"
      pattern: "class ChunkRecord\\(LanceModel\\)"
    - from: "src/corpus_analyzer/store/schema.py"
      to: "lancedb.pydantic.Vector"
      via: "vector: Vector(768)"
      pattern: "Vector\\(768\\)"
---

<objective>
Define the LanceDB ChunkRecord schema and the make_chunk_id() helper using TDD.

Purpose: The schema is the most critical decision in Phase 1 — vector dimensions and metadata fields are baked in at table creation time and cannot be changed without a full rebuild. Getting this right first prevents retrofitting later.

Output: src/corpus_analyzer/store/schema.py with ChunkRecord LanceModel and make_chunk_id(); verified by passing tests.
</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/ROADMAP.md
@.planning/STATE.md
@.planning/phases/01-foundation/01-RESEARCH.md
@.planning/phases/01-foundation/01-CONTEXT.md
</context>

<feature>
  <name>LanceDB ChunkRecord schema and chunk_id helper</name>
  <files>
    src/corpus_analyzer/store/__init__.py
    src/corpus_analyzer/store/schema.py
    tests/store/__init__.py
    tests/store/test_schema.py
  </files>
  <behavior>
    ChunkRecord fields and types:
      chunk_id: str           — sha256(source_name + file_path + str(start_line) + text); merge key
      file_path: str          — absolute path to source file
      source_name: str        — name from corpus.toml [[sources]]
      text: str               — raw chunk text
      vector: Vector(768)     — embedding; nomic-embed-text produces 768-dim vectors
      start_line: int         — 1-indexed start line in source file
      end_line: int           — 1-indexed end line (inclusive) in source file
      file_type: str          — ".md", ".py", ".ts", ".js", ".json", ".yaml"
      content_hash: str       — sha256 of full file content at index time (change detection)
      embedding_model: str    — e.g. "nomic-embed-text"; validated on query
      indexed_at: str         — ISO8601 timestamp string (str not datetime avoids pyarrow coercion)

    make_chunk_id(source_name: str, file_path: str, start_line: int, text: str) -> str:
      Returns sha256 hex digest of the concatenation of all four inputs.
      Two chunks with identical text at different paths produce different IDs.
      Same chunk re-indexed produces the same ID (deterministic).

    Cases:
      make_chunk_id("my-skills", "/home/user/skills/foo.md", 1, "# Title\nHello") -> stable sha256 hex
      make_chunk_id("other-src", "/home/user/skills/foo.md", 1, "# Title\nHello") != same with different source_name
      make_chunk_id("my-skills", "/home/user/bar.md", 1, "# Title\nHello") != same with different path
      ChunkRecord(chunk_id=..., file_path=..., source_name=..., text=..., vector=[0.1]*768, ...) instantiates cleanly
      ChunkRecord with vector of wrong length (e.g. 512 floats) raises validation error
  </behavior>
  <implementation>
    1. Create src/corpus_analyzer/store/__init__.py (empty, just marks as package)
    2. Create src/corpus_analyzer/store/schema.py:
       - Import: lancedb.pydantic.LanceModel, lancedb.pydantic.Vector, hashlib
       - Define make_chunk_id(source_name, file_path, start_line, text) -> str using hashlib.sha256
       - Define ChunkRecord(LanceModel) with all 11 fields above
       - Add module docstring and PEP 257 docstrings on class and function
       - Type annotations on all fields and function signature
    3. Create tests/store/__init__.py (empty)
    4. Create tests/store/test_schema.py with tests for:
       - make_chunk_id determinism (same inputs -> same output)
       - make_chunk_id uniqueness (different source_name, path, or start_line -> different output)
       - ChunkRecord instantiation with valid 768-dim vector succeeds
       - ChunkRecord field names and types are correct (verify via model_fields)
       - chunk_id field exists as str

    Follow RED-GREEN-REFACTOR cycle:
      RED: Write tests first, run pytest (must fail — schema not yet created)
      GREEN: Implement schema.py to pass tests
      REFACTOR: Add docstrings, clean up; confirm tests still pass

    Note: lancedb must be installed. Run `uv add lancedb tomli-w platformdirs` before implementing.
    Note: Do NOT hardcode 768 as a magic number in tests — reference Vector(768) from the schema to keep tests schema-driven.
  </implementation>
</feature>

<verification>
uv run pytest tests/store/test_schema.py -v
uv run mypy src/corpus_analyzer/store/schema.py
uv run ruff check src/corpus_analyzer/store/schema.py
</verification>

<success_criteria>
All tests in tests/store/test_schema.py pass. mypy reports no errors. ChunkRecord exports correctly from store package.
</success_criteria>

<output>
After completion, create .planning/phases/01-foundation/01-01-SUMMARY.md
</output>
