# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

**qc-trace** is a pure Python library (zero runtime dependencies) that normalizes AI CLI session data from multiple tools into a unified schema for consistent querying and analysis. Supported sources: Claude Code, Codex CLI, Gemini CLI, and Cursor IDE.

## Environment

- **Always use `uv`** to run Python. Never use bare `python` or `pip`. Use `/home/sagar/trace/.venv/bin/python` or prefix with `uv run`.
- **Local Postgres**: Start with `docker compose -f analysis/docker-compose.yml up -d` (port 15432, user `localdev`). The `.local.env` DSN points here.

## Commands

```bash
# Run all tests
pytest tests/

# Run a single test file
pytest tests/test_transforms.py

# Run a single test class or function
pytest tests/test_cursor_transform.py::TestExtractSessionId::test_extracts_composer_id_from_path

# Run with coverage
pytest tests/ --cov=qc_trace --cov-report=html

# Lint and format
ruff check --fix qc_trace/
ruff format qc_trace/
```

## Architecture

### Core Pattern: Source Schema → Transform → NormalizedMessage

Every supported CLI tool follows the same module structure under `qc_trace/schemas/{tool_name}/`:

- **`v1.py`** — Frozen TypedDict/dataclass definitions matching the tool's raw format. **Never modify a frozen schema**; create `v2.py` for format changes.
- **`transform.py`** — Functions that convert source data into `list[NormalizedMessage]`. One input record can produce multiple output messages (e.g., a message containing both text and tool calls).
- **`__init__.py`** — Re-exports types and transform functions.

The central target schema is `NormalizedMessage` in `qc_trace/schemas/unified.py`. All transforms produce instances of this dataclass.

### Source Format Differences

| Source | File Format | Transform Style |
|--------|------------|----------------|
| Claude Code | JSONL (one JSON per line) | Line-by-line, stateless |
| Codex CLI | JSONL | Line-by-line with `CodexTransformContext` (tracks state across lines) |
| Gemini CLI | Single JSON file per session | Whole-session transform |
| Cursor IDE | SQLite DBs + text files + JSON files | File-type-specific parsing via `utils/cursor_parser.py` and `utils/sqlite.py` |

### Utilities (`qc_trace/utils/`)

- **`schema_extractor.py`** — Infers JSON/JSONL schema structure from files (debug/exploration tool).
- **`sqlite.py`** — Reads Cursor's VS Code SQLite databases (`state.vscdb`, `ai-code-tracking.db`) with BLOB parsing.
- **`cursor_parser.py`** — Parses Cursor's text-based transcripts, terminal sessions, and MCP tool/server JSON files.

### Dependency Flow

```
utils/cursor_parser → schemas/cursor → schemas/unified
utils/sqlite        (standalone, stdlib sqlite3)
utils/schema_extractor (standalone)
schemas/{claude_code,codex_cli,gemini_cli}/transform → schemas/unified
```

No circular dependencies. All modules depend only on `unified.py` and the standard library.

## Conventions

- **Python ≥ 3.11** required (uses `X | Y` union syntax).
- **Conventional commits** enforced by git hook: prefix with `feat:`, `fix:`, `docs:`, `test:`, `refactor:`, `chore:`, `ci:`, `perf:`, `style:`, `add:`, `update:`, `remove:`.
- **No Claude/Anthropic attribution** in commit messages (blocked by commit-msg hook).
- **`CLAUDE.md` and `PLAN.md` are git-ignored** and blocked from commits by pre-commit hook.
- Test fixtures live in `tests/fixtures/` with sample data for each CLI format.

## Adding a New CLI Source

1. Create `qc_trace/schemas/{tool_name}/v1.py` with frozen TypedDict schemas.
2. Create `qc_trace/schemas/{tool_name}/transform.py` with a `transform_{tool}_v1()` function returning `list[NormalizedMessage]`.
3. Add test fixtures in `tests/fixtures/` and tests in `tests/`.
