datasets-lint 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. datasets_lint-0.1.0/.github/workflows/ci.yml +68 -0
  2. datasets_lint-0.1.0/.github/workflows/publish.yml +32 -0
  3. datasets_lint-0.1.0/.gitignore +14 -0
  4. datasets_lint-0.1.0/.pre-commit-hooks.yaml +7 -0
  5. datasets_lint-0.1.0/CONTRIBUTING.md +48 -0
  6. datasets_lint-0.1.0/LICENSE +21 -0
  7. datasets_lint-0.1.0/PKG-INFO +159 -0
  8. datasets_lint-0.1.0/README.md +123 -0
  9. datasets_lint-0.1.0/ROADMAP.md +173 -0
  10. datasets_lint-0.1.0/action.yml +60 -0
  11. datasets_lint-0.1.0/datasets-lint.toml.example +24 -0
  12. datasets_lint-0.1.0/docs/report.png +0 -0
  13. datasets_lint-0.1.0/pyproject.toml +59 -0
  14. datasets_lint-0.1.0/scripts/make_bench.py +58 -0
  15. datasets_lint-0.1.0/src/datasets_lint/__init__.py +21 -0
  16. datasets_lint-0.1.0/src/datasets_lint/baseline.py +71 -0
  17. datasets_lint-0.1.0/src/datasets_lint/checks/__init__.py +18 -0
  18. datasets_lint-0.1.0/src/datasets_lint/checks/base.py +38 -0
  19. datasets_lint-0.1.0/src/datasets_lint/checks/dup.py +187 -0
  20. datasets_lint-0.1.0/src/datasets_lint/checks/pii.py +134 -0
  21. datasets_lint-0.1.0/src/datasets_lint/checks/stats.py +96 -0
  22. datasets_lint-0.1.0/src/datasets_lint/cli.py +168 -0
  23. datasets_lint-0.1.0/src/datasets_lint/config.py +64 -0
  24. datasets_lint-0.1.0/src/datasets_lint/dataset.py +207 -0
  25. datasets_lint-0.1.0/src/datasets_lint/engine.py +55 -0
  26. datasets_lint-0.1.0/src/datasets_lint/report/__init__.py +8 -0
  27. datasets_lint-0.1.0/src/datasets_lint/report/html_out.py +106 -0
  28. datasets_lint-0.1.0/src/datasets_lint/report/json_out.py +17 -0
  29. datasets_lint-0.1.0/src/datasets_lint/report/templates/report.html.j2 +124 -0
  30. datasets_lint-0.1.0/src/datasets_lint/result.py +110 -0
  31. datasets_lint-0.1.0/tests/fixtures/clean.jsonl +10 -0
  32. datasets_lint-0.1.0/tests/fixtures/dirty.jsonl +12 -0
  33. datasets_lint-0.1.0/tests/test_checks.py +92 -0
  34. datasets_lint-0.1.0/tests/test_cli.py +89 -0
  35. datasets_lint-0.1.0/tests/test_dataset.py +57 -0
@@ -0,0 +1,68 @@
1
+ name: ci
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ concurrency:
9
+ group: ${{ github.workflow }}-${{ github.ref }}
10
+ cancel-in-progress: true
11
+
12
+ jobs:
13
+ test:
14
+ runs-on: ubuntu-latest
15
+ strategy:
16
+ fail-fast: false
17
+ matrix:
18
+ python: ["3.10", "3.11", "3.12", "3.13"]
19
+ steps:
20
+ - uses: actions/checkout@v4
21
+ - uses: astral-sh/setup-uv@v5
22
+ with:
23
+ python-version: ${{ matrix.python }}
24
+ enable-cache: true
25
+ cache-dependency-glob: "pyproject.toml"
26
+ - name: Install
27
+ run: uv pip install -e ".[dev]"
28
+ - name: Lint
29
+ run: uv run ruff check .
30
+ - name: Format check
31
+ run: uv run ruff format --check .
32
+ - name: Tests
33
+ run: uv run pytest --cov=datasets_lint --cov-report=term-missing
34
+
35
+ self-lint:
36
+ # dogfood: the linter must run cleanly on its own fixtures
37
+ runs-on: ubuntu-latest
38
+ steps:
39
+ - uses: actions/checkout@v4
40
+ - uses: astral-sh/setup-uv@v5
41
+ with:
42
+ enable-cache: true
43
+ cache-dependency-glob: "pyproject.toml"
44
+ - name: Audit fixtures
45
+ run: |
46
+ uvx --from . datasets-lint run tests/fixtures/clean.jsonl --report clean.html
47
+ uvx --from . datasets-lint run tests/fixtures/dirty.jsonl --fail-on none --report dirty.html
48
+ - uses: actions/upload-artifact@v4
49
+ with:
50
+ name: self-lint-reports
51
+ path: "*.html"
52
+
53
+ build:
54
+ runs-on: ubuntu-latest
55
+ steps:
56
+ - uses: actions/checkout@v4
57
+ - uses: astral-sh/setup-uv@v5
58
+ - run: uv build
59
+ - name: Smoke-test the wheel in a clean venv
60
+ run: |
61
+ python -m venv /tmp/venv
62
+ /tmp/venv/bin/pip install dist/*.whl
63
+ /tmp/venv/bin/datasets-lint --version
64
+ /tmp/venv/bin/datasets-lint run tests/fixtures/dirty.jsonl --fail-on none
65
+ - uses: actions/upload-artifact@v4
66
+ with:
67
+ name: dist
68
+ path: dist/
@@ -0,0 +1,32 @@
1
+ name: publish
2
+
3
+ on:
4
+ push:
5
+ tags: ["v*"]
6
+
7
+ permissions:
8
+ contents: read
9
+ id-token: write # trusted publishing to PyPI (no API token needed)
10
+
11
+ jobs:
12
+ build:
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - uses: astral-sh/setup-uv@v5
17
+ - run: uv build
18
+ - uses: actions/upload-artifact@v4
19
+ with:
20
+ name: dist
21
+ path: dist/
22
+
23
+ publish:
24
+ needs: build
25
+ runs-on: ubuntu-latest
26
+ environment: pypi
27
+ steps:
28
+ - uses: actions/download-artifact@v4
29
+ with:
30
+ name: dist
31
+ path: dist/
32
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,14 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .venv/
4
+ dist/
5
+ build/
6
+ *.egg-info/
7
+ .pytest_cache/
8
+ .ruff_cache/
9
+ .coverage
10
+ htmlcov/
11
+ *.html
12
+ !docs/**/*.html
13
+ uv.lock
14
+ .DS_Store
@@ -0,0 +1,7 @@
1
+ - id: datasets-lint
2
+ name: datasets-lint
3
+ description: Audit datasets for duplicates, PII and distribution problems
4
+ entry: datasets-lint
5
+ language: python
6
+ pass_filenames: true
7
+ args: ["run", "--fail-on", "error"]
@@ -0,0 +1,48 @@
1
+ # Contributing
2
+
3
+ Thanks for helping! This project is built to be contributed to: every check is a
4
+ single small file, and the plugin API is intentionally tiny.
5
+
6
+ ## Setup
7
+
8
+ ```console
9
+ git clone https://github.com/horizonbymuneeb/datasets-lint
10
+ cd datasets-lint
11
+ uv venv && uv pip install -e ".[dev]"
12
+ pytest
13
+ ruff check . && ruff format .
14
+ ```
15
+
16
+ No GPU, no API keys, no network needed for tests.
17
+
18
+ ## Add a check (the main way to contribute)
19
+
20
+ 1. Copy an existing check, e.g. `src/datasets_lint/checks/stats.py`.
21
+ 2. Implement `run(self, ds: Dataset, ctx: Context) -> CheckResult`.
22
+ - read data via `ds.texts()` or `ds.rows` (already sampled)
23
+ - read thresholds via `ctx.threshold(self.id, "name", default)`
24
+ - return `severity`, a one-line `summary`, numeric `metrics`, and up to ~50 `Finding`s
25
+ - **never** put raw PII in `message` — use `datasets_lint.checks.pii.redact`
26
+ 3. Register it in `src/datasets_lint/checks/__init__.py`.
27
+ 4. Add a tiny fixture under `tests/fixtures/` and a test in `tests/test_checks.py`.
28
+ 5. Document the id, what it finds and its default threshold in `README.md`.
29
+
30
+ That's it — a reviewable PR of ~100 lines.
31
+
32
+ ## Check design rules
33
+
34
+ - **Deterministic**: seed everything; the same input must give the same report.
35
+ - **Bounded**: cap findings and any quadratic work; sample if needed.
36
+ - **Fast**: a check should stay under ~10 s for 100k rows on 4 vCPUs.
37
+ - **Honest**: if a metric is an estimate, say so in the `summary` or `metrics` key name.
38
+ - **Actionable**: the finding message should tell the user what to do, not just what is wrong.
39
+
40
+ ## Ideas that are not built yet
41
+
42
+ `contamination`, `leakage`, `langid`, `schema`, `toxicity`, `dedup-apply`,
43
+ SARIF output, HuggingFace Hub input (`hf:org/name`), WASM playground.
44
+ Open an issue before starting a big one so we don't duplicate work.
45
+
46
+ ## Commit style
47
+
48
+ `check(dup): report cross-split clusters` — conventional-ish, no ceremony.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 datasets-lint contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,159 @@
1
+ Metadata-Version: 2.5
2
+ Name: datasets-lint
3
+ Version: 0.1.0
4
+ Summary: ruff for training data: audit datasets for duplicates, PII, leakage and contamination
5
+ Project-URL: Homepage, https://github.com/horizonbymuneeb/datasets-lint
6
+ Project-URL: Issues, https://github.com/horizonbymuneeb/datasets-lint/issues
7
+ Author: datasets-lint contributors
8
+ License: MIT
9
+ License-File: LICENSE
10
+ Keywords: data-quality,datasets,deduplication,linter,llm,machine-learning,pii
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
21
+ Classifier: Topic :: Software Development :: Quality Assurance
22
+ Requires-Python: >=3.10
23
+ Requires-Dist: datasketch>=1.6
24
+ Requires-Dist: jinja2>=3.1
25
+ Requires-Dist: polars>=1.0
26
+ Requires-Dist: tomli>=2.0; python_version < '3.11'
27
+ Provides-Extra: dev
28
+ Requires-Dist: pytest-cov>=5; extra == 'dev'
29
+ Requires-Dist: pytest>=8; extra == 'dev'
30
+ Requires-Dist: ruff>=0.6; extra == 'dev'
31
+ Provides-Extra: hf
32
+ Requires-Dist: datasets>=2.18; extra == 'hf'
33
+ Provides-Extra: ner
34
+ Requires-Dist: spacy>=3.7; extra == 'ner'
35
+ Description-Content-Type: text/markdown
36
+
37
+ # datasets-lint
38
+
39
+ **ruff for training data.** Audit a dataset for duplicates, PII, distribution problems and
40
+ eval-set contamination — offline, on CPU, in one command, with a CI-friendly exit code.
41
+
42
+ ```console
43
+ $ datasets-lint run train.jsonl --report out.html
44
+ datasets-lint 0.1.0 · train.jsonl
45
+ 120,000 rows audited (of 120,000, seed 0) · text=text label=label
46
+
47
+ ✓ stats 0.42s 120,000 rows · 0.00% empty · worst column 0.0% null
48
+ ✗ dup 12.10s 18.20% near-duplicate (4,211 clusters), 12.00% exact
49
+ ✗ pii 0.91s 0.41% of rows contain PII (email 487, phone 12, api_key 1)
50
+
51
+ verdict: error — 2 errors, 0 warnings, 1 ok · 13.43s
52
+ report → out.html
53
+ ```
54
+
55
+ ## Why
56
+
57
+ ![datasets-lint HTML report](docs/report.png)
58
+
59
+ Every team that fine-tunes or publishes a dataset re-writes the same checks in a
60
+ throwaway notebook: duplicates, PII, contamination. It is slow, inconsistent, and never
61
+ runs in CI. `datasets-lint` packages it once:
62
+
63
+ - **fast** — samples 100k rows by default and finishes in seconds, not hours
64
+ - **offline** — no API keys, no cloud, no data leaves the machine
65
+ - **CI-native** — `exit 1` on threshold breach, JSON output, baseline diffing
66
+ - **readable** — one self-contained HTML report you can attach to a dataset card or a PR
67
+ - **extensible** — one check = one file; the plugin API is ~20 lines
68
+
69
+ ## Install
70
+
71
+ ```console
72
+ uvx datasets-lint --help # run without installing
73
+ # or
74
+ pip install datasets-lint
75
+ ```
76
+
77
+ ## Usage
78
+
79
+ ```console
80
+ datasets-lint run data.jsonl # text summary
81
+ datasets-lint run data.parquet --report out.html # single-file HTML report
82
+ datasets-lint run train.jsonl test.jsonl --checks dup # cross-split duplicate check
83
+ datasets-lint run data.csv --json-out report.json --fail-on warn
84
+ datasets-lint run data.jsonl --baseline prev.json # did this PR make the data worse?
85
+ datasets-lint checks # list checks
86
+ ```
87
+
88
+ Supported inputs: `.jsonl`, `.ndjson`, `.json`, `.csv`, `.parquet`.
89
+
90
+ Exit codes: `0` ok · `1` threshold breached · `2` usage/input error.
91
+
92
+ ## Checks
93
+
94
+ | id | what it finds | default severity |
95
+ |---|---|---|
96
+ | `stats` | empty rows, nulls, length percentiles, label balance | `error` if >1% empty |
97
+ | `dup` | exact + near duplicates (MinHash/LSH), cross-file overlap | `error` if >2% near-dup |
98
+ | `pii` | emails, phones, IPs, IBANs, SSNs, API keys (redacted) | `error` on secrets |
99
+ | `contamination` | overlap with eval sets (GSM8K, MMLU, HumanEval) | _planned for v0.2_ |
100
+
101
+ ## Configuration
102
+
103
+ `datasets-lint.toml` in the working directory (or `--config path`):
104
+
105
+ ```toml
106
+ [defaults]
107
+ sample = 100000
108
+ seed = 0
109
+ fail_on = "error" # none | warn | error
110
+
111
+ [checks.dup]
112
+ threshold = 0.9 # Jaccard threshold for near-duplicates
113
+ num_perm = 64
114
+ shingle_size = 5
115
+ max_near_dup_pct = 2.0
116
+
117
+ [checks.pii]
118
+ max_error_findings_pct = 0.1
119
+ max_warn_findings_pct = 5.0
120
+
121
+ [checks.stats]
122
+ max_empty_pct = 1.0
123
+ max_null_pct = 50.0
124
+ min_label_pct = 1.0
125
+ ```
126
+
127
+ ## Writing a check
128
+
129
+ ```python
130
+ # src/datasets_lint/checks/short.py
131
+ from datasets_lint.checks.base import Check, Context
132
+ from datasets_lint.dataset import Dataset
133
+ from datasets_lint.result import CheckResult, Severity
134
+
135
+
136
+ class ShortRowsCheck(Check):
137
+ id = "short"
138
+ title = "Short rows"
139
+ description = "Flags texts under N characters."
140
+
141
+ def run(self, ds: Dataset, ctx: Context) -> CheckResult:
142
+ limit = ctx.threshold(self.id, "min_chars", 20)
143
+ bad = [i for i, t in ds.texts() if 0 < len(t) < limit]
144
+ pct = round(100 * len(bad) / max(ds.audited_rows, 1), 2)
145
+ return CheckResult(
146
+ check_id=self.id,
147
+ title=self.title,
148
+ severity=Severity.WARN if bad else Severity.OK,
149
+ summary=f"{pct}% of rows are shorter than {limit} chars",
150
+ metrics={"short_pct": pct},
151
+ findings=[], # add Finding(...) entries for samples
152
+ )
153
+ ```
154
+
155
+ Register it in `src/datasets_lint/checks/__init__.py`. Add a fixture and a test — that's it.
156
+
157
+ ## License
158
+
159
+ MIT
@@ -0,0 +1,123 @@
1
+ # datasets-lint
2
+
3
+ **ruff for training data.** Audit a dataset for duplicates, PII, distribution problems and
4
+ eval-set contamination — offline, on CPU, in one command, with a CI-friendly exit code.
5
+
6
+ ```console
7
+ $ datasets-lint run train.jsonl --report out.html
8
+ datasets-lint 0.1.0 · train.jsonl
9
+ 120,000 rows audited (of 120,000, seed 0) · text=text label=label
10
+
11
+ ✓ stats 0.42s 120,000 rows · 0.00% empty · worst column 0.0% null
12
+ ✗ dup 12.10s 18.20% near-duplicate (4,211 clusters), 12.00% exact
13
+ ✗ pii 0.91s 0.41% of rows contain PII (email 487, phone 12, api_key 1)
14
+
15
+ verdict: error — 2 errors, 0 warnings, 1 ok · 13.43s
16
+ report → out.html
17
+ ```
18
+
19
+ ## Why
20
+
21
+ ![datasets-lint HTML report](docs/report.png)
22
+
23
+ Every team that fine-tunes or publishes a dataset re-writes the same checks in a
24
+ throwaway notebook: duplicates, PII, contamination. It is slow, inconsistent, and never
25
+ runs in CI. `datasets-lint` packages it once:
26
+
27
+ - **fast** — samples 100k rows by default and finishes in seconds, not hours
28
+ - **offline** — no API keys, no cloud, no data leaves the machine
29
+ - **CI-native** — `exit 1` on threshold breach, JSON output, baseline diffing
30
+ - **readable** — one self-contained HTML report you can attach to a dataset card or a PR
31
+ - **extensible** — one check = one file; the plugin API is ~20 lines
32
+
33
+ ## Install
34
+
35
+ ```console
36
+ uvx datasets-lint --help # run without installing
37
+ # or
38
+ pip install datasets-lint
39
+ ```
40
+
41
+ ## Usage
42
+
43
+ ```console
44
+ datasets-lint run data.jsonl # text summary
45
+ datasets-lint run data.parquet --report out.html # single-file HTML report
46
+ datasets-lint run train.jsonl test.jsonl --checks dup # cross-split duplicate check
47
+ datasets-lint run data.csv --json-out report.json --fail-on warn
48
+ datasets-lint run data.jsonl --baseline prev.json # did this PR make the data worse?
49
+ datasets-lint checks # list checks
50
+ ```
51
+
52
+ Supported inputs: `.jsonl`, `.ndjson`, `.json`, `.csv`, `.parquet`.
53
+
54
+ Exit codes: `0` ok · `1` threshold breached · `2` usage/input error.
55
+
56
+ ## Checks
57
+
58
+ | id | what it finds | default severity |
59
+ |---|---|---|
60
+ | `stats` | empty rows, nulls, length percentiles, label balance | `error` if >1% empty |
61
+ | `dup` | exact + near duplicates (MinHash/LSH), cross-file overlap | `error` if >2% near-dup |
62
+ | `pii` | emails, phones, IPs, IBANs, SSNs, API keys (redacted) | `error` on secrets |
63
+ | `contamination` | overlap with eval sets (GSM8K, MMLU, HumanEval) | _planned for v0.2_ |
64
+
65
+ ## Configuration
66
+
67
+ `datasets-lint.toml` in the working directory (or `--config path`):
68
+
69
+ ```toml
70
+ [defaults]
71
+ sample = 100000
72
+ seed = 0
73
+ fail_on = "error" # none | warn | error
74
+
75
+ [checks.dup]
76
+ threshold = 0.9 # Jaccard threshold for near-duplicates
77
+ num_perm = 64
78
+ shingle_size = 5
79
+ max_near_dup_pct = 2.0
80
+
81
+ [checks.pii]
82
+ max_error_findings_pct = 0.1
83
+ max_warn_findings_pct = 5.0
84
+
85
+ [checks.stats]
86
+ max_empty_pct = 1.0
87
+ max_null_pct = 50.0
88
+ min_label_pct = 1.0
89
+ ```
90
+
91
+ ## Writing a check
92
+
93
+ ```python
94
+ # src/datasets_lint/checks/short.py
95
+ from datasets_lint.checks.base import Check, Context
96
+ from datasets_lint.dataset import Dataset
97
+ from datasets_lint.result import CheckResult, Severity
98
+
99
+
100
+ class ShortRowsCheck(Check):
101
+ id = "short"
102
+ title = "Short rows"
103
+ description = "Flags texts under N characters."
104
+
105
+ def run(self, ds: Dataset, ctx: Context) -> CheckResult:
106
+ limit = ctx.threshold(self.id, "min_chars", 20)
107
+ bad = [i for i, t in ds.texts() if 0 < len(t) < limit]
108
+ pct = round(100 * len(bad) / max(ds.audited_rows, 1), 2)
109
+ return CheckResult(
110
+ check_id=self.id,
111
+ title=self.title,
112
+ severity=Severity.WARN if bad else Severity.OK,
113
+ summary=f"{pct}% of rows are shorter than {limit} chars",
114
+ metrics={"short_pct": pct},
115
+ findings=[], # add Finding(...) entries for samples
116
+ )
117
+ ```
118
+
119
+ Register it in `src/datasets_lint/checks/__init__.py`. Add a fixture and a test — that's it.
120
+
121
+ ## License
122
+
123
+ MIT
@@ -0,0 +1,173 @@
1
+ # datasets-lint — Roadmap & Build Plan
2
+
3
+ > `ruff` for training data. A fast, CI-friendly linter that audits a dataset for
4
+ > duplicates, PII, label leakage, contamination and distribution problems, then
5
+ > emits a JSON + HTML report and a non-zero exit code when thresholds are breached.
6
+
7
+ **Status:** v0.1 core built — contamination + launch pending · **License:** MIT · **Stack:** Python 3.10+ (uv, polars, datasketch, jinja2)
8
+
9
+ ---
10
+
11
+ ## 1. Problem
12
+
13
+ Before fine-tuning or publishing a dataset, people hand-roll the same checks in
14
+ throwaway notebooks:
15
+
16
+ - exact/near-duplicate rows (inflate loss, leak eval answers)
17
+ - PII (emails, phones, keys) that must not ship
18
+ - train/test contamination against MMLU / GSM8K / HumanEval
19
+ - label leakage (target encoded in a feature)
20
+ - class imbalance, length outliers, empty/near-empty rows
21
+
22
+ Nothing packaged, nothing CI-able, nothing that produces a report a reviewer can read.
23
+ `datasets-lint` is that tool.
24
+
25
+ ## 2. Non-goals (v0.1)
26
+
27
+ - No model training, no GPU, no LLM judges (must run offline on a laptop)
28
+ - No dataset hosting/versioning (we read local files + HF `datasets`)
29
+ - No automatic "fixing" of data — we report, the user decides
30
+ - No web UI/server in v1 (static HTML report only)
31
+
32
+ ## 3. Success criteria
33
+
34
+ | Metric | Target |
35
+ |---|---|
36
+ | 100k-row JSONL audit (4 vCPU) | < 60 s, < 2 GB RSS |
37
+ | Checks shipped in v0.1 | 4 (`stats`, `dup`, `pii`, `contamination`) |
38
+ | Test coverage of core | ≥ 85% |
39
+ | Contributor on-ramp | one new check = one file + one fixture |
40
+ | Install | `uvx datasets-lint` works with zero config |
41
+
42
+ ---
43
+
44
+ ## 4. Phases
45
+
46
+ ### Phase 0 — Skeleton (½ day)
47
+ - [x] Repo init, MIT license, `.gitignore`, README with the one-command demo
48
+ - [x] `pyproject.toml` (hatchling, src-layout, `console_scripts: datasets-lint`)
49
+ - [x] `uv sync`, ruff + pytest config
50
+ - **Exit:** `uv run datasets-lint --help` prints help.
51
+
52
+ ### Phase 1 — Core engine (1 day)
53
+ - [x] `Dataset` loader: `.jsonl`, `.csv`, `.parquet` (HF `datasets` input is backlog)
54
+ - [x] Plugin API:
55
+ `class Check(Protocol): id, name, run(ds, ctx) -> CheckResult`
56
+ - [x] `CheckResult` = severity, summary, metrics dict, findings (capped sample)
57
+ - [x] Registry + config file `datasets-lint.toml` (thresholds, ignore rules)
58
+ - [x] CLI: `datasets-lint run PATH [--checks ...] [--sample N] [--fail-on error]`
59
+ - **Exit:** two dummy checks run end-to-end and print a text summary.
60
+
61
+ ### Phase 2 — First real checks (2–3 days)
62
+ - [x] `stats`: row count, nulls, token-length histogram, class balance, empty rows
63
+ - [x] `dup`: exact hash + MinHash/LSH near-dup, cluster report, % duplicated
64
+ - [x] `pii`: regex pack (email, phone, IBAN, IP, API-key shapes) + optional
65
+ small NER via `[ner]` extra; findings redacted in report
66
+ - **Exit:** on a 50k sample of a public dataset, `dup` and `pii` produce
67
+ plausible numbers in < 30 s.
68
+
69
+ ### Phase 3 — Differentiator: contamination (2 days)
70
+ - [ ] Bundled mini eval-set fingerprints (GSM8K, MMLU sample, HumanEval) as n-gram
71
+ sketches, downloaded on demand and cached
72
+ - [ ] `contamination`: n-gram overlap + MinHash match, per-eval-set hit rate
73
+ - [ ] Clear verdict: "3.1% of rows overlap GSM8K (threshold 1%)"
74
+ - **Exit:** a known-contaminated public dataset is correctly flagged; a clean one is not.
75
+
76
+ ### Phase 4 — Reports (1–2 days)
77
+ - [x] JSON schema (stable, versioned) for machine consumption
78
+ - [x] HTML report via jinja2: summary cards, per-check sections, sample findings,
79
+ histogram charts (inline SVG, no JS deps)
80
+ - [x] `--report out.html`, `--format json|text|html`
81
+ - [x] `--baseline prev.json` diff mode (did this PR make the data worse?)
82
+ - **Exit:** report opens standalone, looks good, includes exit-code rationale.
83
+
84
+ ### Phase 5 — Packaging & distribution (1 day)
85
+ - [x] `uv build` → sdist + wheel; install into a clean venv and run the CLI
86
+ - [x] Extras: `[ner]`, `[hf]`, `[dev]`
87
+ - [ ] TestPyPI dry run → PyPI publish (Trusted Publishing via GitHub OIDC on tag)
88
+ - [ ] GitHub Action (`action.yml`): runs the linter, uploads report as artifact,
89
+ posts a PR comment
90
+ - [ ] pre-commit hook definition
91
+ - **Exit:** `uvx datasets-lint run x.jsonl` works from a clean machine.
92
+
93
+ ### Phase 6 — Launch (1 day)
94
+ - [ ] README: problem → 30-second demo → GIF of the HTML report → install → checks table
95
+ - [ ] `CONTRIBUTING.md` + `docs/writing-a-check.md` (10-line check example)
96
+ - [ ] 5–8 `good first issue` entries (one per missing check)
97
+ - [ ] CI matrix: Python 3.10–3.13, ruff, pytest, self-lint a fixture dataset
98
+ - [ ] Show HN / r/MachineLearning / HF forum post draft with real numbers
99
+ - **Exit:** a stranger can add a check in < 30 minutes using only the docs.
100
+
101
+ ---
102
+
103
+ ## 5. Repo layout
104
+
105
+ ```
106
+ datasets-lint/
107
+ ├── pyproject.toml
108
+ ├── README.md ROADMAP.md CONTRIBUTING.md LICENSE
109
+ ├── action.yml # GitHub Action
110
+ ├── .pre-commit-hooks.yaml
111
+ ├── src/datasets_lint/
112
+ │ ├── cli.py
113
+ │ ├── dataset.py # loaders + sampling
114
+ │ ├── config.py # datasets-lint.toml
115
+ │ ├── registry.py # plugin discovery
116
+ │ ├── result.py # CheckResult, Severity, Report
117
+ │ ├── checks/{stats,dup,pii,contamination}.py
118
+ │ └── report/{json_out.py,html_out.py,templates/report.html.j2}
119
+ ├── tests/{test_cli.py,test_checks/,fixtures/*.jsonl}
120
+ └── .github/workflows/{ci.yml,publish.yml}
121
+ ```
122
+
123
+ ## 6. Key design decisions
124
+
125
+ | Decision | Choice | Why |
126
+ |---|---|---|
127
+ | Data engine | `polars` (+ `pyarrow`) | fast on CPU, low RAM, lazy scans |
128
+ | Near-dup | `datasketch` MinHashLSH | battle-tested, tunable threshold |
129
+ | Sampling | default cap 100k rows, seeded | keeps CI fast and deterministic |
130
+ | Plugin model | plain Python protocol + registry | no entrypoint magic needed for v1 |
131
+ | Reports | jinja2 + inline SVG | single-file, no CDN, offline |
132
+ | Findings cap | 50 samples per check in report | avoid 500 MB HTML files |
133
+ | Exit codes | 0 ok, 1 threshold breach, 2 usage error | CI-friendly |
134
+
135
+ ## 7. Risks & mitigations
136
+
137
+ | Risk | Mitigation |
138
+ |---|---|
139
+ | Near-dup too slow on big files | sampling + LSH band tuning; `--sample` flag; benchmark in CI |
140
+ | Eval-set fingerprints are large | store 5-gram sketches, not text; lazy download + cache |
141
+ | PII regex false positives | severity `warn` by default, `error` only for high-confidence patterns |
142
+ | Scope creep (10 checks) | v0.1 ships 4; everything else is a `good first issue` |
143
+ | Name collision later | `datasets-lint` verified free on PyPI (2026-10-31) |
144
+
145
+ ## 8. Day-by-day (compressed)
146
+
147
+ | Day | Deliverable |
148
+ |---|---|
149
+ | 1 | Phase 0 + Phase 1: skeleton, loader, plugin API, CLI |
150
+ | 2 | `stats` + `dup` working, tests |
151
+ | 3 | `pii`, config file, sampling, benchmarks |
152
+ | 4–5 | `contamination` + eval-set fingerprints |
153
+ | 6 | HTML/JSON reports + baseline diff |
154
+ | 7 | Packaging, TestPyPI, GitHub Action, pre-commit |
155
+ | 8 | Docs, launch README, good-first-issues, publish v0.1.0 |
156
+
157
+ ## 9. Backlog after v0.1
158
+
159
+ `leakage` (target in features) · `dedup-apply` (emit cleaned file) · `langid` mix check ·
160
+ `toxicity` (small model) · `schema` check · SARIF output for GitHub code scanning ·
161
+ HF Hub integration (`datasets-lint run hf:org/name`) · Rust/`polars` fast path ·
162
+ web playground (WASM) · leaderboard of "lint scores" for popular public datasets.
163
+
164
+ ---
165
+
166
+ ## 10. Definition of done for v0.1.0
167
+
168
+ 1. `uvx datasets-lint run data.jsonl --report out.html` works on a clean machine
169
+ 2. 4 checks, documented, each with tests and a fixture
170
+ 3. HTML + JSON report, exit codes correct
171
+ 4. Published on PyPI, tag `v0.1.0`, GitHub Action usable
172
+ 5. README has real benchmark numbers from this VM (4 vCPU / 8 GB)
173
+ 6. ≥ 5 `good first issue`s open