Metadata-Version: 2.4
Name: datajig
Version: 0.5.0.dev1
Summary: Agent-native local dataset workspace with immutable revisions and semantic validation
Requires-Python: >=3.11
Requires-Dist: blake3<2,>=1.0
Requires-Dist: imagehash<5,>=4.3
Requires-Dist: jinja2<4,>=3.1
Requires-Dist: numpy<3,>=2.0
Requires-Dist: pillow<13,>=11
Requires-Dist: scipy<2,>=1.14
Provides-Extra: dev
Requires-Dist: build<2,>=1.2; extra == 'dev'
Requires-Dist: mypy<2,>=1.14; extra == 'dev'
Requires-Dist: ruff<1,>=0.9; extra == 'dev'
Description-Content-Type: text/markdown

# DataJig

<p align="center">
  <img src="assets/datajig-hero.png" alt="A precision data jig aligns heterogeneous inputs into a verified training-ready revision" width="100%">
</p>

<p align="center">
  <strong>Repeatable dataset work for agents.</strong><br>
  面向 Coding Agent 与 Auto Research Agent 的本地数据集工作台。
</p>

Agent 很会改数据，却很难证明自己改的是哪一版、审查的是不是当前内容、是否混入了任务外修改。DataJig 像精密治具一样固定并对齐数据，让一次数据任务成为可验证事务：**声明意图 → 冻结精确 changeset → 语义检查 → seal 不可变 revision**。

| Agent 需要的保证 | DataJig 的回答 |
| --- | --- |
| “我改的是哪份数据？” | 稳定 `ds_...` 身份、全文件 BLAKE3 inventory 和原子 HEAD |
| “这些修改属于哪个任务？” | `chg_...` 意图声明与完整 `changeset_...` 候选 |
| “审查后还有人动过文件吗？” | staged/live identity 不一致立即返回 `UNSTAGED_CHANGES` |
| “这版为什么被接受？” | review、finding、revision 和 provenance 由 content ID 串联 |
| “训练到底用了哪一群样本？” | `recipe_...`、`view_...` 与 `bundle_...` 串联选择语义和实际 shard bytes |

它不是另一个 Git/DVC/Oxen 替代品。版本存储后端可以变化，DataJig 专注的是 Agent 执行数据任务时缺失的控制协议：有界 JSON、显式 next action、确定性身份、过期检测和可追溯接受。

语义审查只是第一个内置 validator，不是产品边界。`imagefolder` adapter 可以识别重命名、标签/划分变化、泄漏、损坏媒体和分布漂移；`jsonl` adapter 以稳定 ID 对齐记录，把 added、removed、modified、moved、坏行和重复 ID 纳入同一个 changeset/check/seal 协议。Universal Inventory v2 继续为其他普通文件提供精确字节身份。

面向 Agent 的 workspace、artifact 和查询协议完全在本地运行，以 Rust 作为权威内核；完整证据保存在文件中，stdout 只返回适合 Agent 上下文窗口的有界 JSON。人工 `compare`、Python API 和 HTML 展示目前仍由 Python 集成层提供。数据不会上传，源数据也不会被工具静默修改。

> `0.5.0-dev` · Rust 1.85+ 权威内核 · Universal Inventory v2 · 本地优先 · 可选 Python 集成层

```mermaid
flowchart LR
    A["Declare intent<br/>chg_..."] --> B["Agent edits<br/>dataset files"]
    B --> C["Stage exact bytes<br/>changeset_..."]
    C --> D{"Semantic check"}
    D -->|fix / restage| B
    D -->|PASS + explicit accept| E["Seal immutable<br/>rev_..."]
    E --> F["Resolve cohort<br/>view_..."]
    F --> G["Verify training input<br/>bundle_..."]
    E --> H["Next research task"]
    X["Unexpected live mutation"] -.->|UNSTAGED_CHANGES| C
```

## JSONL：从看懂到安全接受

Auto Research Agent 可以先只读理解两份数据：

```bash
datajig inspect data/papers.jsonl --id-field paper_id
datajig record-diff baseline/papers.jsonl data/papers.jsonl --id-field paper_id
```

`inspect` 返回稳定的 `records_...` 字节身份、字段类型和 ID 完整性；`record-diff` 按 ID 对齐两版数据，区分 added、removed、modified、moved 和 unchanged。两者都不会输出字段值或原始 ID；`record-diff` 只返回稳定 `rid_...` 与行号，并对明细做硬上限。

需要让 Agent 真正修改并接受一份 JSONL 时，直接初始化单文件 workspace：

```bash
datajig init data/papers.jsonl --id-field paper_id --state .datajig
datajig changeset-begin --state .datajig \
  --intent "Normalize paper metadata" --task-id research-42
# Agent 修改 papers.jsonl
datajig changeset-stage --state .datajig --change chg_...
datajig check --state .datajig --change chg_... --changeset changeset_...
datajig findings .datajig/latest.review.json --offset 0 --limit 50
datajig seal --state .datajig --change chg_... --changeset changeset_... \
  --accept-report review_... --message "Accept normalized records"
```

workspace 不保存原始记录或 ID，而是保存分页的 `recordstate_...` / `recordpage_...` 伪名化事实。坏 JSON、非 UTF-8、缺失/空/复合 ID 和重复 ID 可以被精确 stage 和 review，但必定阻止 seal；审查后发生的任何现场修改都会返回 `UNSTAGED_CHANGES`。

### 用策略约束 Agent 的数据修改

在初始化时用一个不可变策略固定质量门槛：

```json
{
  "namespace": "datajig",
  "schema_version": 1,
  "adapter": "jsonl",
  "mode": "changed_only",
  "fields": {
    "status": {
      "required": true,
      "nullable": false,
      "types": ["string"],
      "enum": ["draft", "published"]
    },
    "score": {"types": ["number"], "minimum": 0, "maximum": 1},
    "doi": {"types": ["string"], "pattern": "^10\\.", "unique": true}
  }
}
```

```bash
datajig init data/papers.jsonl --id-field paper_id \
  --policy data/papers.policy.json --state .datajig
```

`changed_only` 只阻止新增或内容已修改记录带来的违规，适合逐步治理已有历史问题；`full` 要求整个候选都满足策略。唯一性始终在完整候选上计算。策略支持 required、nullability、JSON 类型、typed enum、精确数值范围、Rust regex 和标量唯一性。报告以 `JSONL_POLICY_RANGE` 等聚合 finding 返回字段名、计数和至多 20 个 `rid_...`，不会写入原始 ID 或字段值。策略被保存为不可变 `policy_...` 并进入 workspace 身份，任务中途替换策略、候选或报告都会 fail closed。

### 先封印 cohort，再花训练算力

Agent 不应把一次临时 filter 当成可复现的实验输入。DataJig 用一个有界 JSON recipe 描述 sealed HEAD 上的子集：顶层谓词全部做 AND，比较不做隐式类型转换，missing 与 null 明确区分；可选抽样按 typed record ID 和 seed 稳定执行。

```json
{
  "namespace": "datajig",
  "kind": "subset_view",
  "schema_version": 1,
  "where": [
    {"field": "status", "op": "eq", "value": "published"},
    {"field": "score", "op": "gte", "value": 0.8}
  ],
  "sample": {"rate_bps": 2500, "seed": "research-42"}
}
```

```bash
# 只读预检：先知道会选中多少条，再决定是否启动训练
datajig view-check --state .datajig --recipe recipes/published.json

# 同一 recipe 进入训练 bundle 的身份和离线验证链
datajig export --state .datajig --view recipes/published.json \
  --output artifacts/published-v1 --split train=9000 --split validation=1000
datajig export-info artifacts/published-v1/datajig.bundle.json --verify
```

`view-check` 对空结果返回成功，但标记 `exportable: false`；真正导出空 view 会以 `VIEW_EMPTY` 失败且不创建目录。stdout 只给出计数与 content ID，不回显 record ID、记录值或 recipe 字面量。当前 v1 刻意只支持顶层 `exists` / `missing` / `is_null` / `eq` / `in` / 数值范围和确定性抽样；不接受 SQL、Python UDF、嵌套路径、join 或任意代码执行。

### 从可信 revision 到可验证训练输入

通过审查并 seal 后，可以把当前 clean HEAD 一次性导出为确定性的训练 bundle：

```bash
datajig export --state .datajig --output artifacts/papers-v1 \
  --seed research-42 \
  --split train=9000 --split validation=1000
datajig export-info artifacts/papers-v1/datajig.bundle.json --verify
```

DataJig 按 typed record ID 和 seed 确定 split 与伪随机顺序，统一输出 LF JSONL shards。`datajig.bundle.json` 将 `dataset_id`、sealed revision、record state、质量策略、每个 shard 的记录数/字节数/BLAKE3 和最终 `bundle_...` 串成一条证据链。相同 HEAD 与配置导出到不同目录会得到完全相同的 bundle identity 和 shard bytes；dirty workspace、源文件竞态、已有或重叠输出目录都会 fail closed。

bundle 是标准 JSONL，可直接交给现有生态，不要求训练进程依赖 DataJig：

```python
from datasets import load_dataset

dataset = load_dataset(
    "json",
    data_files={
        "train": "artifacts/papers-v1/train/*.jsonl",
        "validation": "artifacts/papers-v1/validation/*.jsonl",
    },
)
```

首版只导出 keyed JSONL 的当前 sealed HEAD 或它的 sealed subset view。它不假装能够重放未保存原始 bytes 的历史 revision，也不执行任意 SQL 或 Python UDF。完整 HEAD 实验记录 `bundle_id`；子集实验同时记录 `view_id` 与 `bundle_id`，并在训练前运行 `export-info --verify`。

## 60 秒 Agent 工作流

初始化一次。每项 Agent 任务先声明意图，修改完成后把整个候选数据集冻结成 changeset；后续审查与接受始终绑定这两个不可变 ID：

```bash
datajig init data/
datajig changeset-begin --intent "Relabel ambiguous cats" --task-id task-42
# 返回 change_id: chg_...；Agent 现在修改 data/
datajig changeset-stage --change chg_...
# 返回 changeset_id: changeset_...
datajig status --change chg_... --changeset changeset_...
datajig check --change chg_... --changeset changeset_...

# WARN / FAIL 时
datajig plan --change chg_... --changeset changeset_...
# 先把 finding 的伪名化证据解析为当前 staged candidate 的行坐标
datajig findings .datajig/latest.review.json --offset 0 --limit 50
datajig locate .datajig/latest.review.json fnd_... \
  --change chg_... --changeset changeset_...
# Agent 按 line 做最小修复；locate 不返回原始 ID、字段值或整条记录
# 重新 stage，随后重新 check；旧 changeset 不会被悄悄改写

# PASS 且 findings > 0 时，先检查证据
datajig findings .datajig/latest.review.json --offset 0 --limit 50

# 使用 check 返回的精确 report_content_id 接受这份报告
datajig seal --change chg_... --changeset changeset_... \
  --accept-report review_... --message "Accept relabeling task"

# 查看不可变历史
datajig log --limit 20
```

`findings` 是分页接口。Agent 必须检查返回的 `page.has_more`；如果为 `true`，持续将 `--offset` 增加 50，直到读取完 `page.total` 条 finding，再决定是否接受并 seal。

核心命令形成从数据修改到训练输入的闭环：

| 命令 | 回答的问题 | 安全边界 |
| --- | --- | --- |
| `init` | 这是哪个数据集，初始 HEAD 是什么？ | 为目录写入 inventory，或为 keyed JSONL 写入分页 record state；不改源数据 |
| `changeset-begin` | 这次任务为何修改数据？ | 只允许从 clean HEAD 开始；生成确定性的 `chg_...` 意图声明 |
| `changeset-stage` | 这次任务确切改了什么？ | 冻结全数据集候选和变更摘要为不可变 `changeset_...` |
| `status` | 当前内容相对 HEAD 是否变化？ | 纯读取；返回 HEAD/current state ID 和有界文件或记录变更计数 |
| `check` | 相比 last-good 发生了什么？ | 输出策略决策和 finding 数量；PASS 仍可能包含 info finding |
| `plan` | Agent 下一步具体做什么？ | 只生成 `agent_decision` 计划，不自动删除或覆盖文件 |
| `locate` | 这个 finding 对应候选文件的哪几行？ | 严格绑定 report/changeset/live state；只返回伪名化 RID、record hash 和行号 |
| `seal` | 接受的变更可以成为新 revision 吗？ | 重新验证身份；有 finding 时必须绑定精确报告 ID；以 compare-and-swap 推进 HEAD |
| `log` | 这份数据是怎么到当前版本的？ | 分页读取不可变 revision、父链、消息、时间和接受的报告 ID |
| `view-check` | sealed HEAD 上这个 cohort 到底有多大？ | 只读解析有界 recipe；绑定 revision、选择语义和命中记录内容，不输出值 |
| `export` / `export-info` | 训练实际读取的 bytes 是什么？ | 原子生成并离线复验标准 JSONL shards；可把 `view_...` 固化进 bundle |

> **重要：** `PASS` 表示当前策略没有 warning/error，不等于所有语义变化都自动正确。`seal` 是对变化的显式接受，并保证接受的报告没有过期，而不是替 Agent 判断业务意图。新建 inventory 使用 `all_files_v2`，每个普通文件的路径、大小和字节哈希都会进入 identity；schema 1 artifact 仍按 `supported_media_v1` 语义严格读取，并在输出中明确标记覆盖范围。

## 它解决的不是普通文件 diff

普通的文件 diff 很难回答数据集变更中真正重要的问题：

- 一张图片只是改名了，还是内容真的变了？
- 样本是否被移动到错误的标签或 `train` / `val` / `test` 划分？
- 不同划分之间是否出现完全重复或视觉近似的图片？
- 新版本是否加入了损坏图片、异常通道或明显的类别分布漂移？
- 这次变更是否应该阻止 CI 继续运行？

DataJig 将路径、BLAKE3 内容哈希和感知哈希分阶段组合，尽量给出稳定且可解释的答案。

```mermaid
flowchart TB
    DATA["Dataset directory"] --> FILES["Universal file inventory<br/>path · size · BLAKE3"]
    DATA --> ADAPTER["ImageFolder adapter<br/>label · split · media · pHash"]
    FILES --> ID["Deterministic dataset identity"]
    ADAPTER --> REVIEW["Semantic findings + policy"]
    ID --> CHANGESET["Task-scoped changeset"]
    REVIEW --> CHANGESET
    CHANGESET --> REVISION["Immutable revision + provenance"]
    REVISION --> HEAD["Atomic CAS HEAD"]
    AGENT["Coding / Research Agent"] <--> JSON["Bounded JSON protocol"]
    JSON <--> CHANGESET
```

对 Agent 来说，关键差异不只是“检测更多问题”，而是工具本身可以被可靠地调用和恢复：

- **不用把大报告塞进上下文**：完整 artifact 落盘，摘要、分页和样本预览都有硬上限
- **可以稳定引用同一个问题**：finding 使用 `fnd_...`，修复动作使用 `act_...`，相同输入得到相同身份
- **不需要自己维护版本状态**：dataset ID、HEAD、不可变 revisions、最新 review 和 remediation plan 都由工作区协议管理
- **多步任务不会失去身份**：意图声明、全量 staged candidate、review 和最终 revision 由同一组 content ID 串联
- **不会拿旧结论盖章**：`seal` 会重新扫描并校验 baseline 与 candidate 的内容身份
- **并发写入不会静默互相覆盖**：HEAD 使用跨进程锁和 compare-and-swap，过期 Agent 必须重新读取
- **明确的格式边界**：ImageFolder 使用 workspace schema 2，普通 JSONL 使用 schema 3，固定质量策略的 JSONL 使用 schema 4；旧工作区不会被静默迁移或重写
- **可以先发现能力再执行**：`capabilities` 和 `describe` 暴露版本、限制、平台、读写效果与退出码
- **失败可被程序理解**：原生 Agent 命令成功时输出单个 JSON；错误包含稳定的 code、message 和 retryable 字段

## 产品成熟度

当前版本已经具备 **Agent-native dataset workspace 的第一层内核**：Agent 可以发现能力、建立数据集身份、声明任务意图、把完整候选冻结成 changeset、读取 staged/unstaged 状态、遍历不可变历史、得到确定性审查、按 ID 查询证据、把 finding 定位到当前候选行、生成修复计划，并在重新验证内容身份后 seal 被明确接受的 PASS 结果。对象先于引用发布、HEAD 原子替换，损坏 refs、缺失对象和过期输入都会 fail closed。

但它还没有达到“Agent 一用就离不开”的完整产品状态，也还不能被当成无需判断的全自动安全门。接下来最重要的不是堆更多检查器，而是消除信任、采用和闭环上的摩擦：

1. **安全执行与回滚**：在已经可定位的证据上增加 guarded patch preview、CAS apply 和 seal 前精确 undo
2. **扩大零摩擦分发**：四个平台的 Rust-native PyPI wheel 已上线；下一步补齐 Windows 与稳定版发布
3. **进入 Vibe Coding 默认路径**：把已有的 Agent Skill 生成能力升级为一键安装，并补齐 Git hook 和 CI 模板
4. **训练框架适配**：在已稳定的 verified bundle 上提供 PyTorch / Hugging Face 轻量接口
5. **远程适配与产品验证**：消费 Oxen、DVC、Hugging Face 或对象存储 revision，并用真实 Agent 任务衡量价值

我们的产品目标不是“另一个数据集版本控制器”，而是成为 Agent 使用数据的默认控制层：**知道自己在改什么，改动有身份，风险可验证，接受可追溯，失败能恢复。**

## 功能

- **语义化变更识别**：新增、删除、改名、标签变化、划分变化、疑似重编码和视觉编辑
- **数据泄漏检查**：发现跨划分的完全重复与近似重复图片
- **数据质量检查**：报告无法解码的图片、异常通道和不支持的文件
- **JSONL 记录画像**：流式统计字段类型与 ID 完整性，定位坏行和重复 ID，不泄露原始值
- **JSONL 记录事务**：分页 record state、逻辑记录 changeset、语义 review、修复计划与不可变 revision
- **可定位证据**：把 staged JSONL finding 安全解析为有界行坐标，并拒绝过期 report 或现场修改
- **可验证训练导出**：从 clean sealed JSONL HEAD 生成确定性 split/shard、portable manifest 与 `bundle_...`
- **分布对比**：比较标签、划分、格式、尺寸、宽高比和通道分布
- **可视化报告**：生成带前后对照和缩略图的自包含 HTML 文件
- **CI 友好**：输出确定性 JSON，并通过稳定退出码表达审查结果
- **Agent 原生**：能力发现、有界输出、稳定 finding/action ID、显式 next actions
- **全文件身份**：Universal Inventory v2 为每个普通文件记录路径、大小和 BLAKE3
- **任务级 Changeset**：确定性意图声明、全数据集 staging、unstaged 检测和 revision provenance
- **Revision 工作区**：稳定 dataset ID、不可变 inventory/revision、原子 HEAD、status/log 和防过期 seal
- **增量分析**：SQLite 缓存保存媒体元数据与感知哈希，不保存图片内容
- **本地优先**：不会上传数据，也不会修改源数据集

## 安装

当前预览版已经发布到 PyPI。平台 wheel 内置 Rust 内核，安装后不需要本机 Rust 工具链或 `DATAJIG_NATIVE`。由于当前版本仍是预发行版，需要显式允许预发行版本：

```bash
python -m pip install --pre datajig
datajig capabilities
```

需要完全固定版本时使用 `python -m pip install datajig==0.5.0.dev1`。

从源码参与开发：

```bash
git clone https://github.com/liukejun7/DataJig.git datajig
cd datajig
cargo build --release --locked --manifest-path rust/Cargo.toml
python -m pip install -e '.[dev]'
export DATAJIG_NATIVE="$PWD/rust/target/release/datajig-core"
```

发行 wheel 会优先使用包内的 `datajig-core`；源码开发也可以让它位于 `PATH`，或通过 `DATAJIG_NATIVE` 指向可执行文件。找不到 Rust 后端时会明确失败，不会退回 Python 算法。`datajig compare`、Python API 和 HTML 展示目前仍走 Python 实现，不依赖原生 binary。

维护者可显式指定受支持目标构建本机平台 wheel；Linux 发布流水线还会使用 `auditwheel` 在 manylinux2014 环境中审计并生成最终兼容标签：

```bash
DATAJIG_BUILD_TARGET=x86_64-unknown-linux-gnu \
  python -m build --wheel
```

当前 PyPI wheel 矩阵覆盖 Linux x86_64 / aarch64 和 macOS x86_64 / arm64。发布流水线通过 GitHub OIDC Trusted Publishing 上传经过安装验收的 wheel；当前不发布 sdist。

## 人工比较与可视化

准备两份 ImageFolder 数据集：

```text
baseline/                  candidate/
├── train/                 ├── train/
│   ├── cat/               │   ├── cat/
│   │   └── 001.jpg        │   │   └── 001.jpg
│   └── dog/               │   └── dog/
└── val/                   └── val/
    └── cat/                   └── cat/
```

如果需要人工审查两份显式快照，可以直接生成 HTML 和 JSON：

```bash
datajig compare baseline candidate \
  --output review.html \
  --json review.json \
  --cache .cache/datajig.sqlite \
  --workers 4
```

在浏览器中打开 `review.html` 即可查看按风险排序的审查结果。报告包含内联样式和脚本，无需服务器；如果数据敏感，可添加 `--no-thumbnails` 禁止嵌入缩略图。

只需要终端 JSON 时，可以省略输出参数：

```bash
datajig compare baseline candidate
```

## Python API

```python
from datajig import compare

report = compare(
    "baseline",
    "candidate",
    cache=".cache/datajig.sqlite",
    workers=4,
    phash_threshold=6,
)

print(report.policy.status.value)
for finding in report.findings:
    print(finding.severity.value, finding.code, finding.sample_ids)

report.save_html("review.html")
report.save_json("review.json")
```

## 不可变数据集快照

DataJig 可以为目录中的所有文件创建可移植 manifest。snapshot ID 只取决于规范化相对路径、文件大小和 BLAKE3 内容哈希，不包含绝对路径、mtime、worker 数量或运行机器信息。同一份数据复制到不同目录后仍会得到相同 ID。

```bash
datajig snapshot dataset/ \
  --output artifacts/snapshot.json \
  --workers 4
```

读取快照摘要，或在不访问原始数据集的情况下比较两个 manifest：

```bash
datajig snapshot-info artifacts/snapshot.json

datajig snapshot-diff \
  artifacts/before.json \
  artifacts/after.json \
  --offset 0 \
  --limit 50
```

manifest diff 区分 `modified`、`renamed`、`removed` 和 `added`。不同路径但内容完全相同的文件会按确定性一对一规则识别为 rename；重复内容的数量不会被折叠。分页结果最多包含 200 项，并受约 50,000 字符的总输出预算约束。

manifest schema version `1` 的 snapshot ID 使用明确的二进制预映像，后续 Rust 核心必须逐字兼容：

1. 写入 ASCII 前缀 `datajig-snapshot-v1`，随后写入一个 `NUL` 字节。
2. 条目按路径 UTF-8 字节的字典序排列。
3. 每个条目依次写入：4 字节大端无符号路径长度、路径 UTF-8 字节、8 字节大端无符号文件大小、32 字节原始 BLAKE3 内容摘要。
4. 对全部字节计算 BLAKE3，并添加 `snap_` 前缀。

固定兼容向量：`a.txt`（3 字节，摘要为 32 个 `aa` 字节）与 `数据/猫.jpg`（大小 `2^64-1`，摘要为 32 个 `01` 字节）共同生成：

```text
snap_366459218864742c742209d14aad2f9f281c7aaece1e859d7d3b905346b39a5c
```

单条路径最多 4,096 个 UTF-8 字节，单文件大小必须能用无符号 64 位整数表示，单个 manifest 最多 250,000 条记录和 128 MiB。manifest 通过同目录临时文件、`fsync` 和原子替换发布；输出路径不能位于或指向数据集源文件。

## Rust 原生 Agent 内核

v0.5 开始以 `datajig-core` 作为原生 Agent 协议的唯一权威内核。当前 Rust 已实现 `init`、`changeset-begin`、`changeset-stage`、`status`、`check`、`plan`、`locate`、`seal`、`log` 工作区闭环，`view-check` sealed subset、`export` / `export-info` verified training bundle，`inventory`、`review`、`snapshot` 系列 artifact 操作，以及 schema-1 报告的 `findings`、`finding`、`explain` 只读查询；Python 入口对这些命令只做转发，没有算法 fallback。workspace schema 2 保持 ImageFolder 兼容，schema 3 表示普通单文件 JSONL，schema 4 表示固定质量策略的 JSONL；revision/changeset schema 2 使用 `recordstate_...`，旧 schema 1 对象仍按原 identity 读取。Windows 会明确报告暂不支持需要文件句柄安全保证的 workspace 写入，但离线 bundle/report 查询可用。

```bash
cargo build --release --manifest-path rust/Cargo.toml

./rust/target/release/datajig-core capabilities

./rust/target/release/datajig-core inventory dataset/ \
  --output artifacts/inventory.json \
  --threads 8

./rust/target/release/datajig-core review \
  path:baseline/ path:candidate/ \
  --output artifacts/review.json \
  --threads 8

./rust/target/release/datajig-core snapshot dataset/ \
  --output artifacts/snapshot.json \
  --threads 8
```

本地 Linux 环境、8 workers/threads、每组 3 次运行的中位数：

| 数据集 | Python 时间 / 峰值 RSS | Rust 时间 / 峰值 RSS | 时间提升 |
| --- | ---: | ---: | ---: |
| 20,000 个 1 KiB 小文件 | 10.11s / 166 MiB | 0.33s / 26 MiB | 约 31× |
| CIFAR-10 60,000 张图片 | 32.32s / 378 MiB | 0.85s / 63 MiB | 约 38× |

两组迁移基准的 manifest 均逐字一致。这里的数据用于确定迁移边界，并非所有硬件上的性能承诺。manifest 工作流已经只走 Rust；图像审查、匹配和策略模块将按同一原则继续迁移，最终 Python 只保留 API 兼容和 HTML 渲染。

## Agent 协议细节

工作区命令建立在确定性、可分页的 JSON 协议上。`init` 创建自动被 Git 忽略的 `.datajig` 状态；`changeset-begin` 把任务意图锚定到 clean HEAD，`changeset-stage` 冻结候选 inventory 或 record state 和精确摘要。携带 `--change` 与 `--changeset` 后，`status` 会报告 staged/unstaged，`check`、`plan`、`locate` 和 `seal` 都拒绝脱离该候选的现场修改。`check` 返回 `seal`、`inspect`、`fix` 或 `retry`。WARN/FAIL 的下一步是 `plan`：它把完整 review 转成 `.datajig/latest.plan.json`，每个动作都绑定稳定的 `fnd_...` finding ID，并带有稳定的 `act_...` ID、风险、修复策略、样本预览和复验命令。`locate` 再把策略 finding 中的 `rid_...` 或结构 finding 映射为候选文件行号；它不会回显逻辑 ID、字段值或记录内容，也不会接受已经过期的 report。

首版计划刻意采用 `agent_decision` 模式：它会建议去重、修复媒体、整理布局或复核分布，但不会静默删除、移动或覆盖数据。Agent 完成最小修复后重新运行 `check`；`seal` 只接受仍然新鲜的 PASS 报告，同时验证 HEAD baseline 和当前数据都没有在审查后改变。PASS 仍包含 finding 时，`--accept-report` 必须精确匹配最新 `review_...`。成功后旧 revision 保留，`log` 可按父链追溯。

DataJig 在首次公开发布前完成了命名空间硬切；从 `0.5.0-alpha` 开始，`datajig` 命令以及 `ds_...`、`inventory_...`、`rev_...`、`review_...` 等机器身份构成正式兼容承诺。

完整报告保存在状态目录中，Agent 可以按需读取少量 finding，避免把整份数据集审查结果放进上下文窗口。报告解析、finding 排序和紧凑输出均由 Rust 完成；Python 只负责启动已通过能力握手的原生后端。

发现工具能力和协议限制：

```bash
datajig capabilities
```

创建可供 Agent 或后续原生 compare 消费的媒体盘点文件：

```bash
datajig inventory dataset/ \
  --output artifacts/inventory.json \
  --workers 8
```

inventory schema version `2` 为所有普通文件记录规范化路径、大小与 BLAKE3，并为受支持图片附加 ImageFolder 语义、64 位 pHash、尺寸、格式、通道和解码错误。schema 1 仍可严格读取并保留原 content ID；完整结果原子写入文件，stdout 只返回有界摘要，避免大型数据集淹没 Agent 上下文。

直接生成可查询的原生 review artifact：

```bash
datajig review \
  path:baseline/ inventory:artifacts/candidate-inventory.json \
  --output artifacts/review.json \
  --phash-threshold 6
```

`review` 接受 `path:<目录>`、`inventory:<文件>`，裸路径等价于 `path:`。stdout 返回 content ID、状态、finding 数量和推荐的下一步命令；完整 schema-1 报告只写入指定文件。

获取经过筛选的一页 finding：

```bash
datajig findings review.json \
  --severity error \
  --code CROSS_SPLIT_EXACT_LEAKAGE \
  --offset 0 \
  --limit 20
```

每个 finding 都有基于规范化内容生成的稳定 ID。Agent 可以用 ID 单独获取证据，也可以请求有数量上限的紧凑摘要：

```bash
datajig finding review.json fnd_0123456789abcdef0123_0001
datajig explain review.json --limit 10
```

Agent 命令遵循以下约定：

- 成功时 stdout 只包含一个紧凑 JSON 文档
- 错误时 stderr 包含带 `code`、`message` 和 `retryable` 的 JSON
- `findings` 默认返回 50 项，单页最多 200 项
- `explain` 最多携带 20 条 finding，并将紧凑 JSON 控制在约 50,000 字符以内；截断状态会显式返回
- 过滤条件使用交集语义
- 查询只读取报告文件，不会重新访问或修改数据集

Agent 查询的退出码为 `0`（成功）、`2`（参数、文件或报告无效）和 `4`（finding 不存在）。完整报告继续使用 schema version `1`；v0.2 以兼容方式为 finding 增加了 `id` 字段。

## 数据集约定

每份快照使用 `{split}/{label}/...` 目录结构：

- 支持的划分：`train`、`val`、`test`
- 支持的图片：JPEG、PNG、WebP
- 标签目录下允许继续嵌套目录
- 不跟随目录符号链接
- 文件符号链接必须指向当前数据集根目录以内
- 根目录中的其他文件会被统计，但不会作为图片解码

## 审查策略

默认情况下，布局错误、图片解码失败和跨划分完全重复会使审查失败；近似重复、匹配歧义和分布变化会产生警告。可以通过 TOML 文件调整严重级别与阈值：

```toml
[policy]
label_delta_threshold = 0.15
media_delta_threshold = 0.20

[policy.severity]
CROSS_SPLIT_NEAR_LEAKAGE = "error"
LABEL_DISTRIBUTION_CHANGED = "warning"
```

```bash
datajig compare baseline candidate --policy policy.toml
```

CLI 退出码：

| 退出码 | 含义 |
| ---: | --- |
| `0` | 审查完整，结果为通过或警告 |
| `1` | 命中策略中的失败条件 |
| `2` | 输入、布局或配置无效 |
| `3` | 分析未完整完成 |

## 工作原理

匹配按三层进行：相对路径、BLAKE3 内容哈希、pHash 汉明距离。完全相同的内容优先匹配；视觉近似候选使用一对一的最小代价分配；存在多个等价方案时会明确标记为歧义，避免把不确定关系伪装成确定变更。

缓存只保存解码后的尺寸、格式、通道和感知哈希。每次运行仍会流式计算文件的 BLAKE3，因此即使大小和修改时间未变，内容变化也不会错误复用旧分析。

## 当前范围

`0.5.0-dev` 已将全文件 BLAKE3 identity、本地 ImageFolder 安全扫描、媒体元数据和 pHash、keyed JSONL record state 与语义事务、内容寻址 manifest、任务级 changeset、last-good workspace、确定性 remediation plan、字段质量策略、sealed subset、verified training bundle，以及 Agent 报告查询和 finding 定位收归 Rust 内核。当前 JSONL 还没有字段级 diff、自动安全 patch/rollback 或历史原始 bytes 重放。远程数据源、MCP、训练框架薄适配、Oxen / DVC / Hugging Face 适配、目标检测、分割、音视频和学习型 embedding 也尚未包含在当前版本中。
