TypeScript API
Compile a contract and validate Markdown programmatically, gate by gate (lint, shape, state), so LLM autofix is safe by construction.
Cooper: "TARS, what's your honesty setting?" TARS: "Ninety percent." Cooper: "Ninety?" TARS: Absolute honesty isn't always the most diplomatic… but I'll never tick a box I didn't earn.
Compile a contract, check a document, read the verdict.
import { compile } from "mediva";
const schema = compile(`
<!-- mdv: section required content.words.min=20 content.none=@placeholders -->
## Summary
<!-- mdv: endsection -->
`);
const result = schema.check(markdown);
// { path, gate, diagnostics, failing, readout? }
// gate: null passed; "could-not-run" judged nothing; else the gate that failed
if (result.gate !== null) console.error(result.failing); // the findings the verdict rests onGates: lint, shape, state
The gates run in order. gate names the first that failed, and diagnostics holds only its
findings; state never runs on a document whose shape failed. When none fails, gate is null and
diagnostics holds every gate's warnings. gate is "could-not-run" when nothing was judged: the
contract is broken, or the run lacks an input it needs (an error such as missing-title).
| Gate | Asks | Example | Autofix |
|---|---|---|---|
lint | Can mediva read the Markdown? | markdownlint, unclosed-fence, fenced-output | yes |
shape | Does the form match the contract? | missing-section, label-line-mismatch | yes |
state | Does what it claims hold? | empty-section, unchecked-items, value-not-allowed | no |
The lint gate runs first on every document: markdownlint's own rules (default MD005, MD056
and MD046 fenced; the lint option replaces them, and a severity in it throws) plus
mediva's own checks. Its findings are always errors; no contract lowers them.
| Method | Runs |
|---|---|
check(md, options) | every gate: lint, shape, then state |
check(md, { through: "shape" }) | lint, then shape |
| Option | Effect |
|---|---|
path | the document's path, as the result names it |
context | the title, issues and values the contract reads |
document | resolver and source capabilities for grounding rules |
failOn | "warning": any finding fails its gate, so a shape warning stops state (default "error") |
lint | markdownlint's configuration object, replacing the default rule set |
through | "shape": stop once the shape gate passed (no state rule runs) |
schema.check(md, { through: "shape" }).gate // null: the form is right
const r = schema.check(md);
r.gate // null, yet unfinished:
r.diagnostics // [unchecked-items]: a human owns that box
schema.check(md, {
context: { issues: [{ number: 123, state: "open" }], title: "feat: retry uplink" },
failOn: "warning",
});Unticked boxes warn until the contract says error=unchecked-items. Advisory (heuristic) findings stay warnings even under error=.
Once shape passes, readout says what the document claims, so you compare state without parsing
Markdown: { title?, front?, fields: [{ block, kind, present, empty, items?, lines?, rows? }] }.
title is the title the run was given; front lists each declared front-matter key as
{ key, line, value }. A repeated section reports its fields once per instance. A checklist item
carries label, line, ticked, evidence (nested evidence whole), reason and a disposition (done,
claimed-without-evidence, na, not-done, open); a labeled line carries label, line,
value, parts ({ token, value } per mask token), a disposition (given or not-done) and
its reason; a table row carries line and cells by column header. A template slot (<why>) or
a stub (TODO) is never a reason: it is placeholder-text.
Grounding: schema. bind()
Bind the source names a contract spells to data or loaders (grounded templates).
const bound = await schema.bind({
sources: {
openIssues: ["123", "456"], // literal data
changedFiles: async ({ signal }) => fetchFiles(signal), // loader
crew: { kind: "file", path: "/abs/crew.json", members: ["name"] }, // a file, projected (needs `read`)
},
context: { breaking: false },
});
const report = bound.check(md); // sync: loaders resolved once, at bind| Option | Effect |
|---|---|
| (default) | binding-unknown, binding-missing or a source that fails to load throws GroundingError |
{ partial: true } | reports them instead |
{ templatePath, read } | loads the contract's . path refs (relative to templatePath) and file bindings through read |
{ kind: "loader", load, timeoutMs, optional } | optional: true turns its failure into a source-skipped note for a ? ref; a required ref still fails |
Inspecting document state
schema. is the same check, with how each declared field stands.
const { gate, failing, fields, document } = schema.inspect(md);
// fields[]: { label, kind, present, empty, diagnostics }: a field fails when one of its
// diagnostics is in `failing` (the run's one verdict)
// document: Diagnostic[] for frontmatter and structureLLM autofix: check through shape
Show the model only form errors and it cannot fake state (the full loop).
let r = schema.check(md, { through: "shape" });
if (r.gate !== null && r.gate !== "could-not-run") {
md = await llmFix(md, r.failing); // only FORM errors reach the model
r = schema.check(md, { through: "shape" });
}
const state = schema.check(md); // for a human, not for the modelRendering: renderMarkdown
{ rules: true } keeps the rules as plain-English comments, for prompting an agent.
import { renderMarkdown } from "mediva";
renderMarkdown(contract); // the contract minus its hidden rules
renderMarkdown(contract, { rules: true });
// ## Description
// <!-- rules: required · at least 20 words · no placeholders (TODO, TBD…) -->Plugins
compile takes plugins (authoring).
import plugin from "mediva-plugin-compliance";
const schema = compile(template, { plugins: [plugin] });