Reference
Usage
The same runtime runs from the command line, from TypeScript, and from an MCP-capable agent. Every surface takes text and returns the same findings object.
Install
# one-line install (installs the `datapact` binary)$ curl -fsSL https://raw.githubusercontent.com/srivtx/datapact/main/install.sh | sh# run once, without installing$ bunx github:srivtx/datapact#main --help# install globally$ bun add -g github:srivtx/datapact# add as a project dev dependency$ bun add -d github:srivtx/datapact
datapact is not published to npm, and it requires Bun. The install script drops a datapact binary on your PATH; bunx runs it straight from GitHub.
Commands
| Command | Description |
|---|---|
datapact check --contract <file> --data <csv> | Lint the contract and validate the dataset against it. The combined findings are reported. |
datapact lint --contract <file> | Lint the contract on its own. No data is read. |
datapact mcp | Run the MCP server over stdio, exposing contract_lint and contract_check. |
Flags
| Flag | Description |
|---|---|
--contract <path> | The ODCS contract to read, as YAML or JSON. |
--data <path> | The CSV dataset to validate. Required for check. |
--json | Print a machine-readable JSON document to stdout. |
--quiet, -q | Print a single summary line instead of the full report. |
--sarif <path> | Write a SARIF 2.1.0 report to <path>. |
--fail-on <level> | error (default), warning, info, or none — the exit-code threshold. |
-h, --help / -v, --version | Print help or the version and exit. |
--fail-on affects only the exit code. Every finding is still reported and included in JSON and SARIF output.
Examples
# lint a contract on its own$ datapact lint --contract spec/example-contract.yamlcontract.yaml rows:0 errors:0 warnings:0 info:0# lint and validate together$ datapact check --contract spec/example-contract.yaml --data spec/example-data.csv# machine-readable output for a program$ datapact check --contract contract.yaml --data orders.csv --json# write SARIF and fail CI on warnings too$ datapact check --contract contract.yaml --data orders.csv --sarif datapact.sarif --fail-on warning
A full check
Text output is line-oriented: a summary header, then one finding per line with its severity, code, location, and message.
$ datapact check --contract contract.yaml --data data.csvdata.csv rows:5 errors:16 warnings:0 info:1INFO DP-202 discount_code column "discount_code" is not declared in the contractERROR DP-208 orders.order_id row 2: value "ORD-2" does not match pattern ^ORD-[0-9]{6}$ERROR DP-205 orders.order_id row 4: duplicate value "ORD-000101" in unique column "order_id"ERROR DP-210 orders.customer_email row 3: value "not-an-email" is not a valid emailERROR DP-204 orders.customer_email row 4: required column "customer_email" is emptyERROR DP-209 orders.status row 3: value "refunded" is not in the enum for "status"ERROR DP-203 orders.item_count row 2: "three" is not a valid integerERROR DP-206 orders.item_count row 3: value "0" is below minimum 1ERROR DP-201 orders.is_gift required column "is_gift" is missing from the datasetERROR DP-203 orders.order_date row 3: "2026-13-40" is not a valid date
Exit codes
| Code | Meaning |
|---|---|
0 | No finding at or above --fail-on. |
1 | At least one finding at or above --fail-on. |
2 | Invalid usage, or input that could not be parsed (DP-PARSE-000). |
3 | I/O error: an input file could not be read, or a report could not be written. |
An unreadable or structurally invalid contract is never reported as clean: it produces a DP-PARSE-000 error and exit code 2, so a gate cannot pass on input the tool did not understand.
JSON output
With --json, the result is one stable document: the contract and dataset labels, the row count, the severity counts, and every finding with its code, severity, location, and message. Field order is stable, so a diff means a real change.
{
"contract": "contract.yaml",
"dataset": "data.csv",
"rows": 5,
"counts": { "error": 16, "warning": 0, "info": 1 },
"issues": [
{
"code": "DP-208",
"severity": "error",
"location": "orders.order_id",
"message": "row 2: value \"ORD-2\" does not match pattern ^ORD-[0-9]{6}$"
}
]
}
Library
The engine is exported for your own TypeScript, and it is browser-safe: no node: imports, so the same code runs in a page and in Bun. Parse once, then lint and validate.
import { parseContract, parseCsv, lintContract, validateDataset } from "datapact";
const contract = parseContract(contractSource);
const dataset = parseCsv(csvSource);
const issues = [...lintContract(contract), ...validateDataset(contract, dataset)];
for (const issue of issues) {
console.log(issue.code, issue.severity, issue.message);
}
The high-level helpers wrap the same steps and return a ValidationResult, an object of { contract, dataset, rows, issues, counts }.
import { check, lintOnly, formatText, formatJson, toSarif, PARSE_ERROR_CODE } from "datapact";
const result = check(contractSource, csvSource, {
contractName: "contract.yaml",
datasetName: "data.csv",
});
console.log(formatText(result));
const document = JSON.parse(formatJson(result));
const sarif = toSarif(result, "datapact", "0.1.0");
const lint = lintOnly(contractSource, "contract.yaml");
const parsed = result.issues.some((issue) => issue.code === PARSE_ERROR_CODE);
MCP server
datapact mcp speaks the Model Context Protocol over stdio, so an agent can lint and check without a shell. It exposes two tools: contract_lint takes the contract text (and an optional name) and returns the findings; contract_check additionally takes the CSV text, including its header row.
{
"mcpServers": {
"datapact": {
"command": "bunx",
"args": ["github:srivtx/datapact#main", "mcp"]
}
}
}
| Tool | Arguments | Returns |
|---|---|---|
contract_lint | contract (string, required), name (string, optional) | The lint findings as text. |
contract_check | contract (string, required), csv (string, required), name (string, optional) | The combined lint and validation findings as text. |