Skip to content

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 positions
decision.read(src, { path: "D-0001.md" }); // typed Doc: frontmatter + body model

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:

  1. 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 to path:line, JSON or SARIF output, CI-ready exit codes.
  2. 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.
  3. Read documents through the inferred typed model. The contract types what it checks: read() returns a Doc whose frontmatter, section prose, and table rows are ordinary typed reads — no re-parsing, no second definition to drift.
  4. 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.
  5. Infer contracts from the docs you already have. markdown-contract init reads an existing folder and writes the tightest config that accepts it; --check turns the same inference into a CI drift guard.
  6. 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.
  • 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.

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.

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:

  1. Validate from the terminal — 5 examples, starting with Check a folder of docs.
  2. Author contracts in YAML — 5 examples, starting with Frontmatter and required sections.
  3. Read markdown as typed data — 4 examples, starting with One contract, two doors.
  4. Automate and embed — 4 examples, starting with Embed the corpus runner.