{"id":51594385,"url":"https://github.com/drbaher/contract-lint-cli","last_synced_at":"2026-07-11T17:02:18.696Z","repository":{"id":361663837,"uuid":"1246849864","full_name":"DrBaher/contract-lint-cli","owner":"DrBaher","description":"Lint a contract for internal-consistency defects — placeholders, broken cross-references, undefined/unused defined terms, numbering gaps, party-name \u0026 date inconsistencies — with CI-gateable exit codes. Deterministic, stdlib-only. Also composes with the contract-ops suite.","archived":false,"fork":false,"pushed_at":"2026-07-10T16:56:45.000Z","size":388,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-07-10T18:12:28.690Z","etag":null,"topics":["agent-first","ci","cli","contract-ops","contracts","legal-tech","linter","python","quality-gate","sarif"],"latest_commit_sha":null,"homepage":"https://cli.drbaher.com","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/DrBaher.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-05-22T16:14:18.000Z","updated_at":"2026-07-10T16:57:52.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/DrBaher/contract-lint-cli","commit_stats":null,"previous_names":["drbaher/contract-lint-cli"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/DrBaher/contract-lint-cli","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DrBaher%2Fcontract-lint-cli","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DrBaher%2Fcontract-lint-cli/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DrBaher%2Fcontract-lint-cli/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DrBaher%2Fcontract-lint-cli/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/DrBaher","download_url":"https://codeload.github.com/DrBaher/contract-lint-cli/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DrBaher%2Fcontract-lint-cli/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35368768,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-11T02:00:05.354Z","response_time":104,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["agent-first","ci","cli","contract-ops","contracts","legal-tech","linter","python","quality-gate","sarif"],"created_at":"2026-07-11T17:02:16.867Z","updated_at":"2026-07-11T17:02:18.691Z","avatar_url":"https://github.com/DrBaher.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# contract-lint\n\n**Lint a contract for internal-consistency defects — before anyone signs it.** Point\nit at a draft and it flags leftover placeholders, cross-references that go nowhere,\ndefined terms that are never defined (or never used), duplicate definitions, gaps in\nsection numbering, party names spelled three different ways, and impossible dates —\neach as a finding with a stable rule id, a severity, and a line. It exits non-zero when\nsomething is wrong, so a CI pipeline or an agent can gate on it.\n\nDeterministic and offline: no model, no network, no telemetry. The same input always\nproduces the same byte-for-byte report, so you can diff it in CI.\n\n- **Stdlib only.** Zero runtime dependencies; the core is fully functional with nothing installed.\n- **Single file.** `contract_lint_cli.py` — no DB, no daemon, no SaaS.\n- **CI-gateable.** `--check` (exit-code only), `--fail-on error|warning|none`, `--json`, and `--sarif` for code-scanning.\n- **Reads the text.** `.md` / `.txt` / `.html` / `.docx` natively; `.pdf` via an optional extra. It lints the *original* numbering, cross-references, and defined-term casing — not a normalized model, and for `.docx` including the list numbers Word computes at render time rather than stores.\n\n\u003e **Part of the contract-ops suite — optional.** contract-lint stands on its own, but it\n\u003e also composes with the [contract-ops CLI suite](https://github.com/DrBaher): it is the\n\u003e pre-signature **quality gate** the suite was missing — where\n\u003e [`compare-cli`](https://github.com/DrBaher/compare-cli) gates *drift between versions*,\n\u003e contract-lint gates *defects within one document*. It shares the suite's agent\n\u003e conventions (`--catalog json`, `--json`, exit codes) and can lint a draft or\n\u003e extract-cli's source text. See [`docs/INTEROP.md`](docs/INTEROP.md).\n\n---\n\n## Run this\n\n```bash\npipx run contract-lint demo     # zero-config: lint a deliberately-flawed sample contract\n# or, installed:  pip install contract-lint \u0026\u0026 contract-lint demo\n```\n\nThat lints a bundled, deliberately-broken contract (no file, no network, no model) and\nprints every kind of finding. Then point it at your own draft:\n\n```bash\ncontract-lint your-contract.md\ncontract-lint your-contract.md --check \u0026\u0026 echo \"ready to sign\"   # exit-code gate\n```\n\n## Where to go next\n\n- **New here?** Keep reading — [Quick start](#quick-start) and [The rules](#the-rules).\n- **Driving it from an agent?** See [`AGENTS.md`](AGENTS.md). Call `contract-lint --catalog json`\n  at startup to discover commands/flags, and `contract-lint rules --json` to discover rule ids\n  (don't hardcode them). The `--json` report follows [`docs/spec/lint-output.schema.json`](docs/spec/lint-output.schema.json).\n  There's also an [MCP server](mcp/) (`lint_contract` / `list_rules` / `lint_demo` tools).\n- **Wiring it into CI?** [`docs/recipes/`](docs/recipes/) — a [GitHub Action](docs/recipes/github-actions.md)\n  (with code-scanning), a [pre-commit hook](docs/recipes/pre-commit.md), and [gating any CI/shell](docs/recipes/ci-gate.md).\n- **Wiring it into the pipeline?** [`docs/INTEROP.md`](docs/INTEROP.md) — it sits as a\n  pre-signature gate (after draft-cli, alongside compare-cli, before sign-cli) and emits SARIF.\n- **Contributing?** [`CONTRIBUTING.md`](CONTRIBUTING.md) and [`ARCHITECTURE.md`](ARCHITECTURE.md).\n\n---\n\n## Install\n\n```bash\npip install contract-lint                  # zero dependencies, fully functional on .md/.txt/.html/.docx\npip install \"contract-lint[pdf]\"           # + .pdf reading (pulls in extract-cli's PDF backend)\n```\n\nRequires Python 3.9+. `.md` / `.txt` / `.html` / `.docx` (and text piped on stdin) read with\nno dependencies at all — the `.docx` reader resolves Word's automatic list numbering, which\npython-docx cannot see. Only `.pdf` reading is delegated to\n[`extract-cli`](https://github.com/DrBaher/extract-cli)'s document backend. You can also\nconvert first: `extract contract.pdf | contract-lint -`.\n\n---\n\n## Quick start\n\n```bash\ncontract-lint demo                         # lint the bundled flawed sample\ncontract-lint draft.md                     # human table report\ncontract-lint draft.md --json | jq .summary\ncontract-lint draft.md --sarif \u003e lint.sarif\ncat draft.md | contract-lint - --format md # read from stdin\n\n# Lint several at once (merged report, worst exit code wins):\ncontract-lint contracts/*.md --check\n\n# CI gate: fail the build only on errors (default), or on warnings too:\ncontract-lint draft.md --check                  # exit 0 clean / 1 if errors / 2 unreadable\ncontract-lint draft.md --check --fail-on warning\n```\n\n---\n\n## The rules\n\nEach finding is `{ rule, severity, message, line, column?, excerpt }`. Discover them\nlive with `contract-lint rules --json`.\n\n| Rule | Severity | Default | What it catches |\n|---|---|---|---|\n| `placeholder` | error | on | Leftover unfilled placeholders: `[Bracketed]`, `{{mustache}}`, `\u003cANGLE\u003e`, `____` blanks, `TBD`, `[•]`. |\n| `broken-xref` | error | on | A cross-reference (`Section 7.2`, `Article IV`, `Exhibit B`, `Schedule 2.1`, `clause 9.3`, `Annex 1`) that points to something not present in the document. |\n| `undefined-term` | warning | **off** | A capitalized defined-style term used but never defined. Off by default (proper-noun-prone); enable it for documents with a formal definitions section. |\n| `unused-definition` | warning | on | A term defined but never used afterward. |\n| `double-definition` | warning | on | A term defined more than once. |\n| `numbering` | warning | on | Gaps or duplicates in a heading-number sequence. |\n| `duplicate-heading` | warning | on | Two headings with the same title (often a copy-paste left unedited). |\n| `party-consistency` | warning | on | Defined party names used with variant spellings (`Acme Corporation` vs `Acme Corp.` vs `ACME Corporation`). |\n| `date-sanity` | warning | on | Impossible or inconsistent dates (malformed, or an expiration before the effective date). |\n| `number-consistency` | warning | on | A written-out amount that disagrees with its parenthetical figure, e.g. `thirty (45) days`. |\n| `signature-block` | warning | **off** | A complete-looking contract with no signature/execution block. Off by default (most useful as a final pre-signature check). |\n\n`undefined-term` and `signature-block` ship **off** — the former is the most\nfalse-positive-prone, the latter is opinionated and noisy on fragments. Enable either per\nrun (`--enable undefined-term`) or in config.\n\n---\n\n## Output \u0026 exit codes\n\n- **Default**: a human-readable table on **stdout**. Errors/warnings about the *run* (unreadable\n  file, bad usage), `--why` rationale, and the demo banner go to **stderr**.\n- **`--json`**: the locked, schema'd report on stdout ([`docs/spec/lint-output.schema.json`](docs/spec/lint-output.schema.json)).\n  No timestamp — the report is byte-stable and diffable.\n- **`--sarif`**: SARIF 2.1.0 on stdout for GitHub code-scanning / CI annotations\n  ([`docs/spec/lint-sarif.schema.json`](docs/spec/lint-sarif.schema.json)). Mutually exclusive with `--json`.\n- **`--check`**: print nothing; communicate purely via the exit code.\n\n| Code | Meaning |\n|------|---------|\n| `0` | Clean — no findings at or above `--fail-on`. |\n| `1` | Gate tripped — findings at or above `--fail-on` (default: `error`). |\n| `2` | Bad usage / unreadable input (no such file, bad flag, `--json` + `--sarif`, a `.pdf` without the extra). |\n\n`--fail-on error` (default) trips on errors only; `--fail-on warning` trips on any\nfinding; `--fail-on none` never trips (report-only). **Branch on the exit code, not on the\nhuman-readable message.**\n\n---\n\n## Configuration\n\nOptional, in precedence order (later wins): suite-wide\n`~/.config/contract-ops/contract-lint.json` → project `.contract-lint.json` (found by\nwalking up from the linted file, like git) → `--config PATH` → `--enable`/`--disable`\nflags. See [`config/contract-lint.json.example`](config/contract-lint.json.example):\n\n```jsonc\n{\n  \"rules\": {\n    \"undefined-term\": { \"enabled\": true },          // turn the opt-in rule on\n    \"placeholder\":    { \"enabled\": true, \"severity\": \"error\" },\n    \"party-consistency\": false                       // disable a rule\n  },\n  \"ignore\": [\"Force Majeure\", \"Annex [0-9]\"]         // drop findings whose message matches these regexes\n}\n```\n\n```bash\ncontract-lint draft.md --enable undefined-term --disable numbering\n```\n\n### Inline suppression\n\nA comment on (or above) a line suppresses findings there — eslint-style:\n\n```markdown\nFee is {{rate}} per hour.  \u003c!-- contract-lint: disable-line placeholder --\u003e\n\u003c!-- contract-lint: disable-next-line broken-xref --\u003e\nSee Schedule 9 for details.\n\u003c!-- contract-lint: disable-file undefined-term --\u003e\n```\n\n`disable` / `disable-line` (this line), `disable-next-line` (the next line),\n`disable-file` (the whole document). Name one or more rule ids, or omit them to suppress\nall rules. Only known rule ids are honored, so trailing comment syntax (`--\u003e`, `*/`) is ignored.\n\n---\n\n## Composability\n\n```bash\n# Lint a draft straight out of draft-cli, before rendering/signing:\ndraft fill nda --vars vars.json | contract-lint - --format md --check\n\n# Lint the source text of any document extract-cli can read:\nextract counterparty.pdf | contract-lint - --format md\n\n# Gate a whole folder of drafts in CI (fail on the first defective one):\nfor f in contracts/*.md; do contract-lint \"$f\" --check || exit 1; done\n\n# Emit SARIF for GitHub code-scanning:\ncontract-lint draft.md --sarif \u003e contract-lint.sarif\n```\n\nIn a GitHub workflow, the bundled action lints, uploads SARIF to code-scanning, and gates:\n\n```yaml\n- uses: DrBaher/contract-lint-cli@v0.2.0\n  with: { paths: contracts/, fail-on: error }   # needs permissions: security-events: write\n```\n\nIt's also a pre-commit hook (`id: contract-lint`). See [`docs/recipes/`](docs/recipes/).\n\n---\n\n## Shell completion\n\n```bash\neval \"$(contract-lint completion bash)\"   # bash\neval \"$(contract-lint completion zsh)\"    # zsh\n```\n\n(There is also a hidden `contract-lint __complete …` helper that the scripts call.)\n\n---\n\n## Interop\n\nThe cross-CLI contracts live under [`docs/spec/`](docs/spec/) as JSON Schema 2020-12 and\nare registered in [`docs/INTEROP.md`](docs/INTEROP.md):\n\n- **`--json` report:** [`lint-output.schema.json`](docs/spec/lint-output.schema.json)\n- **rule catalog:** [`rules.schema.json`](docs/spec/rules.schema.json) (`rules --json`)\n- **SARIF:** [`lint-sarif.schema.json`](docs/spec/lint-sarif.schema.json) (`--sarif`)\n\ncontract-lint never calls a model. A future opt-in LLM rule (off by default, never on a hot\npath) would reuse the suite's shared `~/.config/contract-ops/llm.json`.\n\n---\n\n## Development\n\n```bash\nmake install      # editable install with dev extras\nmake test         # full test suite\nmake typecheck    # mypy --strict\nmake coverage     # tests under coverage + report\nmake build        # wheel + sdist\nmake smoke        # build, install the wheel in a clean venv, run it\nmake spec-check   # validate --json/SARIF/rules output against docs/spec schemas (offline)\n```\n\nSee [`ARCHITECTURE.md`](ARCHITECTURE.md) and [`CONTRIBUTING.md`](CONTRIBUTING.md).\n\n## License\n\n[MIT](LICENSE) © DrBaher\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdrbaher%2Fcontract-lint-cli","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdrbaher%2Fcontract-lint-cli","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdrbaher%2Fcontract-lint-cli/lists"}