markdown-contract
Teams keep their durable knowledge in markdown — decision records, runbooks, planning docs, changelogs. It is the cheapest format people actually keep writing. But the moment you need to rely on those documents — trust their structure, or read them as data — markdown gives you nothing, and you end up with ad-hoc regex, a bespoke linter, or a heavyweight CMS.
markdown-contract is the missing middle. You declare a contract per document type — frontmatter fields, section structure, table shapes, custom rules — and one parse gives you back both:
- Validation — findings pinned to
path:line, as human text, JSON, or SARIF, with CI-ready exit codes. - A typed model — the contract that checks a document also types it:
doc.frontmatter.status,doc.body.Summary.text(), iterable typed table rows.
import { contract, sections, section } from "markdown-contract";import { z } from "zod";
const decision = contract({ frontmatter: z.object({ status: z.enum(["proposed", "accepted"]) }), body: sections({ allowUnknown: true }, [section("Summary"), section("Decision")]),});
decision.validate(src, { path: "D-0001.md" }); // findings with path:line positionsdecision.read(src, { path: "D-0001.md" }); // typed Doc: frontmatter + body modelThe functionality, in layers
Section titled “The functionality, in layers”Everything rides on one contract engine, and the surface stacks in layers — adopt the bottom one in minutes with zero code, and climb as your needs grow:
- Declare and validate — no code. Write a contract per document type in
YAML, map folders to contracts in one config file, and run
markdown-contract validate: findings pinned topath:line, JSON or SARIF output, CI-ready exit codes. - Author in TypeScript, inject custom rules. The code API
adds what data can’t express: arbitrary Zod schemas, nested grammars, and named
rules (
rule,docRule,requires/forbids) injected at runtime for cross-cutting policy. - Read documents through the inferred typed model. The contract types what
it checks:
read()returns aDocwhose frontmatter, section prose, and table rows are ordinary typed reads — no re-parsing, no second definition to drift. - Generate templates from contracts (in progress). A contract already declares a document’s full shape — frontmatter fields, section order, table columns — so the same declaration can emit a valid, empty skeleton for new documents: the authoring dual of validation.
- Infer contracts from the docs you already have.
markdown-contract initreads an existing folder and writes the tightest config that accepts it;--checkturns the same inference into a CI drift guard. - Manage vaults from a UI. A local dashboard — and a desktop app — watch your folders and show live validation status over the same engine, for the people who never open a terminal. See Architecture.
Start here
Section titled “Start here”- Why markdown-contract — the problems it solves and what shaped it.
- How it works — one parse, three cooperating planes, one finding shape, and the typed model.
- Architecture — the layers, the import direction, and how the pieces of the workspace fit together.
- Getting started — validate a folder from the terminal, then author a contract in YAML or TypeScript.
Reference
Section titled “Reference”When you want the spec rather than a walkthrough, the reference section documents every command, field, export, rule id, and dialect construct: CLI, Declarative YAML, Library API, typed model, findings & rule ids, dialect, and the glossary.
Appendix: worked examples
Section titled “Appendix: worked examples”Small, self-contained examples, each regression-checked against the real CLI and library. Browse the whole set on the all examples page, or start with a group:
- Validate from the terminal — 5 examples, starting with Check a folder of docs.
- Author contracts in YAML — 5 examples, starting with Frontmatter and required sections.
- Read markdown as typed data — 4 examples, starting with One contract, two doors.
- Automate and embed — 4 examples, starting with Embed the corpus runner.