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

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

The three commands.
CommandDescription
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 mcpRun the MCP server over stdio, exposing contract_lint and contract_check.

Flags

Every flag datapact accepts. A value flag takes both --flag value and --flag=value.
FlagDescription
--contract <path>The ODCS contract to read, as YAML or JSON.
--data <path>The CSV dataset to validate. Required for check.
--jsonPrint a machine-readable JSON document to stdout.
--quiet, -qPrint 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, --versionPrint 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

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
$ 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

The exit code is the CI gate.
CodeMeaning
0No finding at or above --fail-on.
1At least one finding at or above --fail-on.
2Invalid usage, or input that could not be parsed (DP-PARSE-000).
3I/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"]
    }
  }
}
The two MCP tools.
ToolArgumentsReturns
contract_lintcontract (string, required), name (string, optional)The lint findings as text.
contract_checkcontract (string, required), csv (string, required), name (string, optional)The combined lint and validation findings as text.