# AGENTS.md
Guidance for AI coding agents (Claude Code, Codex, Cursor, ...) working in this
repository. Humans should start from [README.md](README.md) and
[docs/CONTRIBUTING.md](docs/CONTRIBUTING.md); this file is the condensed,
agent-oriented version and the single source of truth for agent instructions.
## What this project is
`dataprof` is a local, deterministic data profiling and quality assessment
library: a Rust workspace with a PyO3-based Python package on top. The release
surface is Rust library crates plus Python wheels — there is **no CLI binary**.
It profiles data and reports on it; it does not transform, clean, or move data.
Keep changes within that identity.
`examples/` are executable docs: CI runs them, so keep them working.
## Build, test, lint
Prerequisites: Rust 1.96+, Python 3.11+, [uv](https://docs.astral.sh/uv/).
Local development runs on CPython 3.15, pinned in `.python-version`, which uv
reads and installs on its own; ty still checks against the 3.11 floor.
1.96 is the MSRV (`rust-version` in `Cargo.toml`). CI lints and tests on the
toolchain pinned in `rust-toolchain.toml`, and rustup reads that file, so plain
`cargo` in this checkout runs the same compiler as CI. Clippy gates the build
with `-D warnings` and every Rust release adds lints; a `cargo +<version>`
command or a `RUSTUP_TOOLCHAIN` variable overrides the file, so when clippy
disagrees with CI, check `rustc --version` first.
The MSRV itself is enforced by compilation, not by the declaration: the `MSRV
Compile Gate` job runs `cargo check --locked --lib` over every published feature
graph on 1.96. Reproduce it with
`RUSTUP_TOOLCHAIN=1.96 uv run --no-project python .github/scripts/msrv_check.py`. If a change needs
a newer standard-library API, raising the MSRV is a deliberate separate change —
see [docs/CONTRIBUTING.md](docs/CONTRIBUTING.md#rust-toolchains-msrv-vs-pinned).
```bash
uv sync # install the Python dev environment
uv run maturin develop # build + install the local Python extension
# Focused tests (prefer these; match the command to your change)
cargo test -p dataprof-core
cargo test -p dataprof-python
uv run pytest python/tests/test_python_api.py -q
# Gates that CI enforces — run before committing
cargo fmt --all
cargo clippy --all --all-targets -- -D warnings
uv run ruff format python/ .github/scripts/ .claude/skills/dataprof/scripts/
uv run ruff check python/ .github/scripts/ .claude/skills/dataprof/scripts/
uv run ty check python/
```
Don't run every expensive workspace check for a small docs or Python-only
change; run what matches the change and list the commands in the PR body.
## Conventions
- **Commits**: conventional commits (`feat:`, `fix:`, `docs:`, `ci:`,
`chore:`, with optional scope like `fix(python):`). Release notes are
generated by git-cliff from commit subjects, so a non-conventional subject
can silently vanish from the changelog. Keep messages brief. Do **not** add
AI attribution trailers (`Co-Authored-By: Claude`, "Generated with...").
- **Branches**: `feature/...`, `fix/...`, `docs/...`, `perf/...`. PRs target
`master`. One behavior change per PR; no drive-by refactors.
- **Error handling**: never swallow decode/parse errors into defaults
(`.ok()`, `unwrap_or_default()` on decode paths). A silent error that turns
into a plausible number is the worst bug class for a profiler.
- **Semantics**: `None`/absent means "not analyzed"; empty means "analyzed,
nothing found". Preserve this distinction in reports and bindings.
- **Output contract**: the serialized, rounded metric values must be identical
across engines and input paths for the same logical data, analysis options,
and analyzed population. Rust Serde output and Python `to_dict()`/`to_json()`
govern this contract; source/execution provenance is compared separately.
Native raw floats retain full precision and may differ with accumulation
order. Parity tests compare serialized values exactly; raw diagnostics use
explicit tolerances (relative `1e-9`, absolute `1e-12`). Preserve absence as
well as numbers. See [the numeric contract](docs/schema/README.md#numeric-equality-contract).
If you touch an engine or parser, check the other paths for parity.
- **PR descriptions**: derive behaviour from the code the issue names, not
from the issue text alone. Every sentence of the description must be true of
the diff, and each acceptance criterion of the issue is either met (say
where) or stated as left out. See
[Understanding Your Change](docs/CONTRIBUTING.md#understanding-your-change).
## Project board rules
- Milestones are **releases only** (e.g. `0.9.1`). Do not create thematic
milestones.
- Themes/initiatives live in **epic issues** (e.g. "Epic: Agent & LLM
Integration"); link related issues from the epic body.
- No version-specific labels; use `area: *` and `type: *` labels.
## Key docs
- [docs/CONTRIBUTING.md](docs/CONTRIBUTING.md) — full contributor workflow.
- [docs/release-notes.md](docs/release-notes.md) — what the latest published
release shipped, including breaking changes. It is rewritten at release time
from the merged PRs, so do not edit it in a feature or fix PR, and do not ask
for an entry in review: an unreleased change added there is credited to a
release that does not contain it. Describe behaviour changes, and any
re-baselining they require, in the PR description instead.
- [docs/SECURITY.md](docs/SECURITY.md) — supported versions, reporting.