mediva is now in public preview.Read the announcement →
// cheat sheet

mediva, one problem at a time

Each card: a real problem, the contract that catches it, and what mdv check prints. All run in CI.

npm i -D mediva · X.mdv.md checks X.md · _template.mdv.md checks its folder · ✗ error fails CI · ⚠ warning reports

a directive
<!-- mdv: section required content.words.min=20 -->## Summary

An HTML comment above the heading it governs: a kind, then rules. Rendered Markdown hides it. The section runs to the next heading of its level or higher; write endsection only to end it sooner.

target.op=value

Most rules are props: what to measure (words, items, a column), how, and the limit.

@vocabulary

Checks and word lists that ship with mediva are named with @. Local rule sets use bare names with use=name.

define · include
<!-- mdv: define prose required content.words.min=20 --><!-- mdv: include ./shared.mdv.md#security -->

Name a bundle of rules once and apply it with use=prose; pull a section in from another contract.

Kinds: document · section · list · table · code · line · media · front. Flags are bare words: required, allowEmpty, recommended, na, strict, ordered, title, and numbered on a list.

Say which headings a document needs, in what order, and when.

PR description must say how it was tested

.github/pull_request_template.md
<!-- mdv: section required -->## Testing<!-- mdv: list required content.none=@placeholders items.all=@evidence/specific -->- Describe what you ran and what you saw.
$gh pr view --json body -q .body | npx mdv check - --pr
  • ✗7:1error: Placeholder "- Describe what" left in "Testing"placeholder-text
  • ⚠8warning: "Tested it" can't be checkedvague-evidence

The template is its own contract. A prompt left in fails; “Tested it” only warns.

detailsRun it in GitHub Actions →
<!-- mdv: section required -->## Testing<!-- mdv: list required content.none=@placeholders items.all=@evidence/specific -->- Describe what you ran and what you saw.
$gh pr view --json body -q .body | npx mdv check - --prexit 1
<stdin>  State: what the document claims; clear it by doing the work or saying it is not done  7:1  error    Placeholder "- Describe what" left in "Testing"  placeholder-text                help: Write the real content, or "Not specified." if unknown    8  warning  "Tested it" can't be checked                     vague-evidence                help: Say what you ran or saw, e.g. "npm test: 312 passed" ✖ 2 problems (1 error, 1 warning)  mdv explain <rule-id> for details
Try it in the playground ↗

An ADR must weigh at least two options

adr.mdv.md
<!-- mdv: section required -->## Options<!-- mdv: section occurs=2.. heading.mask="Option <num>: <text>" -->### Option 1: Keep polling
  • ✗3error: 1 heading matches "Option <num>: <text>" under "Options", needs at least 2too-few-repeats

occurs= counts the sections whose heading fits the mask.

detailsSection structure →
<!-- mdv: section required -->## Options<!-- mdv: section occurs=2.. heading.mask="Option <num>: <text>" -->### Option 1: Keep polling
$npx mdv check adr.mdexit 1
adr.md  State: what the document claims; clear it by doing the work or saying it is not done  3  error  1 heading matches "Option <num>: <text>" under "Options", n…  too-few-repeats            help: Add only instances the document really has ✖ 1 problem (1 error, 0 warnings)  mdv explain <rule-id> for details
Try it in the playground ↗

Breaking change? Then a migration guide is required

release.mdv.md
<!-- mdv: section required -->## Type<!-- mdv: list required checked.eq=1 -->- [ ] Breaking change<!-- mdv: section requiredWhen.checked="Breaking change" -->## Migration
  • ✗1error: Missing section "Migration", required because "Breaking change" is checkedmissing-conditional-section
detailsSection structure →
<!-- mdv: section required -->## Type<!-- mdv: list required checked.eq=1 -->- [ ] Breaking change<!-- mdv: section requiredWhen.checked="Breaking change" -->## Migration
$npx mdv check release.mdexit 1
release.md  State: what the document claims; clear it by doing the work or saying it is not done  1  error  Missing section "Migration", required because "Breaking cha…  missing-conditional-section            help: Write the section, or correct the claim that requires it if that claim is wrong ✖ 1 problem (1 error, 0 warnings)  mdv explain <rule-id> for details
Try it in the playground ↗

Every runbook needs an owner and rollback steps

runbooks/_template.mdv.md
<!-- mdv: section required --># Runbook<!-- mdv: line required label="Owner:" mask="<mention>" --><!-- mdv: section required -->## Rollback<!-- mdv: list required numbered -->1. Revert the deploy.
  • ✗1error: Missing section "Rollback"missing-section

A missing Rollback is the form, reported first; an owner that isn't an @handle is a claim, judged once the form is right.

detailsRunbook recipe →
<!-- mdv: section required --># Runbook<!-- mdv: line required label="Owner:" mask="<mention>" --><!-- mdv: section required -->## Rollback<!-- mdv: list required numbered -->1. Revert the deploy.
$npx mdv check runbooksexit 1
runbooks/payments.md  Shape: fix the form; this does not change what the document claims  1  error  Missing section "Rollback"  missing-section            help: Add or rename a heading to "## Rollback" ✖ 1 problem (1 error, 0 warnings)  mdv explain <rule-id> for details
Try it in the playground ↗

ADR sections in a fixed order, nothing extra

adr.mdv.md
<!-- mdv: document strict ordered --><!-- mdv: section required -->## Context<!-- mdv: section required -->## Decision
  • ✗1error: Sections are out of orderout-of-order
  • ✗9error: Section "Notes" is not allowed hereunexpected-section

strict rejects undeclared sections; ordered wants Context before Decision.

detailsADR recipe →
<!-- mdv: document strict ordered --><!-- mdv: section required -->## Context<!-- mdv: section required -->## Decision
$npx mdv check adr.mdexit 1
adr.md  Shape: fix the form; this does not change what the document claims  1  error  Sections are out of order            out-of-order            help: Order them: Context → Decision  9  error  Section "Notes" is not allowed here  unexpected-section            help: Move the content into a declared section, or remove the section ✖ 2 problems (2 errors, 0 warnings)  mdv explain <rule-id> for details
Try it in the playground ↗

Screenshots required, unless there is nothing to see

pr.mdv.md
<!-- mdv: section required -->## Screenshots<!-- mdv: media required na -->![before and after](shot.png)
  • ✗3error: "Screenshots" has no image or video, and no "(N/A: {reason})"missing-media

na accepts “(N/A: reason)” when there is nothing to show.

detailsStrictness →
<!-- mdv: section required -->## Screenshots<!-- mdv: media required na -->![before and after](shot.png)
$npx mdv check pr.mdexit 1
pr.md  State: what the document claims; clear it by doing the work or saying it is not done  3  error  "Screenshots" has no image or video, and no "(N/A: {reason}…  missing-media            help: Add the real screenshot or video, or the empty-state marker if none exists ✖ 1 problem (1 error, 0 warnings)  mdv explain <rule-id> for details
Try it in the playground ↗

alsorecommendedoccurs=1..3requiredWhen.status=publishedforbiddenWhen.front.status=draftexactlyOne=kindendsection

A heading with a placeholder under it is not an answer.

Nobody may ship TODO or the template's own prompt

notes.mdv.md
<!-- mdv: section required content.words.min=12 content.none=@placeholders -->## Summary
  • ✗3:1error: Placeholder "TODO:" left in "Summary"placeholder-text
  • ⚠3warning: "Summary" has 6 words, needs at least 12too-few-words
detailsTags and rules →
<!-- mdv: section required content.words.min=12 content.none=@placeholders -->## Summary
$npx mdv check notes.mdexit 1
notes.md  State: what the document claims; clear it by doing the work or saying it is not done  3:1  error    Placeholder "TODO:" left in "Summary"     placeholder-text                help: Write the real content, or "Not specified." if unknown    3  warning  "Summary" has 6 words, needs at least 12  too-few-words                help: Add the missing specifics, not filler ✖ 2 problems (1 error, 1 warning)  mdv explain <rule-id> for details
Try it in the playground ↗

Untick work that was not done, and say why

pr.mdv.md
<!-- mdv: section required -->## Checklist<!-- mdv: list required labels.eq=@template items.none=bare-tick -->- [ ] Tests run<!-- mdv: endlist -->
  • ✗3error: "Tests run" is ticked with no evidencetick-without-evidence

“(not done: reason)” keeps the label and records why.

detailsEvidence lists →
<!-- mdv: section required -->## Checklist<!-- mdv: list required labels.eq=@template items.none=bare-tick -->- [ ] Tests run<!-- mdv: endlist -->
$npx mdv check pr.mdexit 1
pr.md  State: what the document claims; clear it by doing the work or saying it is not done  3  error  "Tests run" is ticked with no evidence  tick-without-evidence            help: Run what shows it now and record the result, or untick: (not done: reason) ✖ 1 problem (1 error, 0 warnings)  mdv explain <rule-id> for details
Try it in the playground ↗

Every action item needs an owner and a due date

postmortem.mdv.md
<!-- mdv: section required -->## Action items<!-- mdv: list required items.mask="<mention> <text> by <date>" -->- @owner does the thing by 2026-10-01
  • ✗4error: "Tune the retry thresholds" gives no <mention> or <date>value-not-given

A mask is a readable shape: <mention>, <date>, <num>, <text>, …

detailsitems.mask →
<!-- mdv: section required -->## Action items<!-- mdv: list required items.mask="<mention> <text> by <date>" -->- @owner does the thing by 2026-10-01
$npx mdv check postmortem.mdexit 1
postmortem.md  State: what the document claims; clear it by doing the work or saying it is not done  4  error  "Tune the retry thresholds" gives no <mention> or <date>  value-not-given            help: Write the value that is true, or say the work is not done ✖ 1 problem (1 error, 0 warnings)  mdv explain <rule-id> for details
Try it in the playground ↗

Flag checklist ticks that record no evidence

pr.mdv.md
<!-- mdv: section required -->## Checklist<!-- mdv: list required items.all=done-or-na -->- [ ] Tests added
  • ✗3error: "Tests added" is ticked with no evidencetick-without-evidence
  • ✗4error: "Docs updated" is open: unticked, with no reason givenopen-item

Tick with what was run; “(N/A: reason)” only if it does not apply.

detailsEvidence lists →
<!-- mdv: section required -->## Checklist<!-- mdv: list required items.all=done-or-na -->- [ ] Tests added
$npx mdv check pr.mdexit 1
pr.md  State: what the document claims; clear it by doing the work or saying it is not done  3  error  "Tests added" is ticked with no evidence                tick-without-evidence            help: Run what shows it now and record the result, or untick: (not done: reason)  4  error  "Docs updated" is open: unticked, with no reason given  open-item            help: Finish it now and record what shows it, or keep it open: (not done: reason) ✖ 2 problems (2 errors, 0 warnings)  mdv explain <rule-id> for details
Try it in the playground ↗

Nobody deletes the checklist item they skipped

release.mdv.md
<!-- mdv: section required -->## Release checklist<!-- mdv: list required strict -->- [ ] Changelog updated- [ ] Rollback tested
  • ✗3error: Missing item "Rollback tested"missing-checklist-item
  • ✗4error: Item "Announced in Slack" is not in the templateunexpected-checklist-item

strict on a list: exactly the items the contract lists.

detailsEvidence lists →
<!-- mdv: section required -->## Release checklist<!-- mdv: list required strict -->- [ ] Changelog updated- [ ] Rollback tested
$npx mdv check release.mdexit 1
release.md  Shape: fix the form; this does not change what the document claims  3  error  Missing item "Rollback tested"                    missing-checklist-item            help: Rename the line that means it to "Rollback tested"  4  error  Item "Announced in Slack" is not in the template  unexpected-checklist-item            help: Rename it to the template's item, or move it out of the list ✖ 2 problems (2 errors, 0 warnings)  mdv explain <rule-id> for details
Try it in the playground ↗

alsocontent.words.max=120content.none=/TODO|FIXME/content.mask="Fixes <issue>"help="Say what you ran."items.min=2checked.min=1items.none=bare-ticklabels.eq=@templateitems.all=@evidence/citedissue.keyword=[Fixes, Closes]

What a model leaves behind when nobody reads its output.

The agent left “Sure, here's…” and got cut off

notes.mdv.md
<!-- mdv: document content.none=@agent/chatter content.none=@agent/cut-off --><!-- mdv: section required -->## Changes
  • ⚠1:1warning: Leftover assistant phrasing "Sure, here's"agent-chatter
  • ⚠5warning: Document looks cut off mid-sentencetruncated-output

Advisory: these annotate, and block only with --fail-on warning.

detailsValidate LLM output →
<!-- mdv: document content.none=@agent/chatter content.none=@agent/cut-off --><!-- mdv: section required -->## Changes
$npx mdv check notes.mdexit 0
notes.md  Shape: fix the form; this does not change what the document claims  1:1  warning  Leftover assistant phrasing "Sure, here's"  agent-chatter                help: Remove the chatter  State: what the document claims; clear it by doing the work or saying it is not done    5  warning  Document looks cut off mid-sentence         truncated-output                help: Restore the missing ending from the full content ✖ 2 problems (0 errors, 2 warnings)  mdv explain <rule-id> for details
Try it in the playground ↗

Unwrap the answer the agent put in a code fence

notes.mdv.md
<!-- mdv: document content.none=@agent/repair --><!-- mdv: section required -->## Changes
$npx mdv fix notes.md --dry-run
  • ↻1would be repairedfenced-output

@agent/repair opts in to safe repairs; mdv fix applies them.

detailsmdv fix →
<!-- mdv: document content.none=@agent/repair --><!-- mdv: section required -->## Changes
$npx mdv fix notes.md --dry-runexit 0
mdv: dry run - no files will be written notes.md  would fix [fenced-output] line 1 --- a/notes.md+++ b/notes.md@@ -1,5 +1,3 @@-```markdown ## Changes  Webhook deliveries now retry with exponential backoff.-```   0 problems would remain 1 file would change, 1 fix would be applied, 0 problems would remain
Try it in the playground ↗

alsocontent.none=/As an AI/

The agent writes “seamless, game-changing” fluff

post.mdv.md
<!-- mdv: section required content.none.phrases=./banned.txt -->## Launch
banned.txt
seamlessgame-changingleverage
  • ✗3:5error: "seamless" found; this section must contain none of the phrases in "./banned.txt"forbidden-content
  • ✗3:15error: "game-changing" found; this section must contain none of the phrases in "./banned.txt"forbidden-content
detailscontent.none.phrases →
<!-- mdv: section required content.none.phrases=./banned.txt -->## Launch
$npx mdv check post.mdexit 1
post.md  State: what the document claims; clear it by doing the work or saying it is not done   3:5  error  "seamless" found; this section must contain none of the phr…  forbidden-content               help: Say what is true instead, or drop the claim  3:15  error  "game-changing" found; this section must contain none of th…  forbidden-content               help: Say what is true instead, or drop the claim ✖ 2 problems (2 errors, 0 warnings)  mdv explain <rule-id> for details

Gate an agent's answer before it is saved

reply.mdv.md
<!-- mdv: section required content.words.min=8 -->## Answer
$your-agent | npx mdv check - --schema reply.mdv.md
  • ✗1error: Missing section "Answer"missing-section

- reads stdin; exit 1 means fix and retry.

detailsCLI › stdin →
<!-- mdv: section required content.words.min=8 -->## Answer
$your-agent | npx mdv check - --schema reply.mdv.mdexit 1
<stdin>  Shape: fix the form; this does not change what the document claims  1  error  Missing section "Answer"  missing-section            help: Add or rename a heading to "## Answer" ✖ 1 problem (1 error, 0 warnings)  mdv explain <rule-id> for details
Try it in the playground ↗

Sums that add up, names that exist, dates that are fresh.

Invoice line items must add up to the total

invoice.mdv.md
<!-- mdv: section required -->## Items<!-- mdv: table required @tables/total=Amount error=total-mismatch -->| Item | Amount || --- | --- || Total | $0.00 |
  • ✗7error: Column "Amount" sums to 6,000.00, but the Total row states 6,500.00total-mismatch

The Total row is the claim; the rows above it are summed.

detailsTables recipe →
<!-- mdv: section required -->## Items<!-- mdv: table required @tables/total=Amount error=total-mismatch -->| Item | Amount || --- | --- || Total | $0.00 |
$npx mdv check invoice.mdexit 1
invoice.md  State: what the document claims; clear it by doing the work or saying it is not done  7  error  Column "Amount" sums to 6,000.00, but the Total row states…  total-mismatch            help: Recompute the column and fix the wrong cell or the Total ✖ 1 problem (1 error, 0 warnings)  mdv explain <rule-id> for details
Try it in the playground ↗

Only @mention people who are on the team

status.mdv.md
<!-- mdv: document content.scan="<mention|in: ./team.txt>" -->
team.txt
@dana@lee@priya
  • ✗3error: "@jordan" not found in "./team.txt"unknown-entity
detailsGrounding →
<!-- mdv: document content.scan="<mention|in: ./team.txt>" -->
$npx mdv check status.mdexit 1
status.md  State: what the document claims; clear it by doing the work or saying it is not done  3  error  "@jordan" not found in "./team.txt"  unknown-entity            help: Correct a misspelt name to the source's spelling; never swap in another name ✖ 1 problem (1 error, 0 warnings)  mdv explain <rule-id> for details

A runbook nobody verified in 90 days is stale

runbook.mdv.md
<!-- mdv: section required content.dates.age.max=90d -->## Last verified
$npx mdv check runbook.md --now 2026-09-28
  • ✗3error: "Last verified" was last verified 210 days ago, limit is 90 daysstale

--now pins the clock for a reproducible run.

detailsRunbook recipe →
<!-- mdv: section required content.dates.age.max=90d -->## Last verified
$npx mdv check runbook.md --now 2026-09-28exit 1
runbook.md  State: what the document claims; clear it by doing the work or saying it is not done  3  error  "Last verified" was last verified 210 days ago, limit is 90…  stale            help: Re-check the section against reality, then update its date ✖ 1 problem (1 error, 0 warnings)  mdv explain <rule-id> for details

The reply must quote the real refund amount

reply.mdv.md
<!-- mdv: section required content.claims.in=./ticket.json -->## Resolution
ticket.json
{ "id": "INV-2041", "refund": "$42.50", "date": "2026-09-30" }
  • ✗3error: "$45.00" (amount) not found in "./ticket.json"ungrounded

content.claims.in checks every amount, date and id against the source.

detailsGrounding →
<!-- mdv: section required content.claims.in=./ticket.json -->## Resolution
$npx mdv check reply.mdexit 1
reply.md  State: what the document claims; clear it by doing the work or saying it is not done  3  error  "$45.00" (amount) not found in "./ticket.json"  ungrounded            help: Copy the value exactly from the source ✖ 1 problem (1 error, 0 warnings)  mdv explain <rule-id> for details

The PR may only name files it actually changed

pr.mdv.md
<!-- mdv: document content.paths.in=@change/paths -->
$npx mdv check body.md --schema pr.mdv.md --change-set main...HEAD
  • ✗3error: "src/queue.ts" not found in "@change/paths"not-in-change-set

--change-set feeds the paths and refs a git range touched.

detailsGrounding →
<!-- mdv: document content.paths.in=@change/paths -->
$npx mdv check body.md --schema pr.mdv.md --change-set main...HEADexit 1
body.md  State: what the document claims; clear it by doing the work or saying it is not done  3  error  "src/queue.ts" not found in "@change/paths"  not-in-change-set            help: Change the named file, or say the work is not done; never swap in a changed path ✖ 1 problem (1 error, 0 warnings)  mdv explain <rule-id> for details

Every citation must point at a source

memo.mdv.md
<!-- mdv: document content.refs.all=defined -->
  • ✗3error: Citation points to nothingunresolved-citation

Each [^note] and [text][ref] needs its definition.

detailscontent.refs →
<!-- mdv: document content.refs.all=defined -->
$npx mdv check memo.mdexit 1
memo.md  State: what the document claims; clear it by doing the work or saying it is not done  3  error  Citation points to nothing  unresolved-citation            help: Add the missing definition, or replace it with the real URL ✖ 1 problem (1 error, 0 warnings)  mdv explain <rule-id> for details
Try it in the playground ↗

alsocontent.entities.in=./names.jsoncontent.eq=./wording.txtcontent.quotes.in=./interview.mdrows.in=./metrics.jsoncontent.paths.in=@change/paths@glossary/terms=./glossary.json@numbers/percent@numbers/sums

Front matter, tables, code blocks, titles and heading shapes.

Front matter: known keys, allowed status, ISO date

_template.mdv.md
<!-- mdv: front status required in=[draft, review, approved] --><!-- mdv: front updated required date=iso --><!-- mdv: front strict -->
  • ✗3error: Frontmatter "updated" value "28/09/2026" is a <date> written another wayvalue-wrong-form
  • ✗4error: Unknown frontmatter key "reviewer"unexpected-frontmatter-key

front strict rejects keys the contract doesn't declare.

detailsFront matter →
<!-- mdv: front status required in=[draft, review, approved] --><!-- mdv: front updated required date=iso --><!-- mdv: front strict -->
$npx mdv check rfc-12.mdexit 1
rfc-12.md  Shape: fix the form; this does not change what the document claims  3  error  Frontmatter "updated" value "28/09/2026" is a <date> writte…  value-wrong-form            help: Write it "2026-09-28"  4  error  Unknown frontmatter key "reviewer"                            unexpected-frontmatter-key            help: Move what it says into the declared key it belongs to ✖ 2 problems (2 errors, 0 warnings)  mdv explain <rule-id> for details
Try it in the playground ↗

Status table: the right columns, allowed values

status.mdv.md
<!-- mdv: section required -->## Workstreams<!-- mdv: table required columns.has=[Area, Owner, Status] rows.Status.in=[todo, doing, done] -->
  • ✗6error: Column "Status" value "blocked?" is not one of the allowed valuesvalue-not-allowed
detailsTables recipe →
<!-- mdv: section required -->## Workstreams<!-- mdv: table required columns.has=[Area, Owner, Status] rows.Status.in=[todo, doing, done] -->
$npx mdv check status.mdexit 1
status.md  State: what the document claims; clear it by doing the work or saying it is not done  6  error  Column "Status" value "blocked?" is not one of the allowed…  value-not-allowed            help: Write whichever is true: todo, doing, done ✖ 1 problem (1 error, 0 warnings)  mdv explain <rule-id> for details
Try it in the playground ↗

alsoheading.mask="ADR-<num>: <text>"content.lead.mask="<word|oneOf: [feat, fix]>: <text>"title.words.min=3rows.min=1rows.Owner.mask="<mention>"rows.Owner.requiredrows.Area.unique

Code samples must name their language

guide.mdv.md
<!-- mdv: section required -->## Example<!-- mdv: code required lang.in=[ts, sh] -->
  • ✗3:4error: Language "js" not allowed in "Example", allowed: ts, shcode-lang-not-allowed
detailsCode blocks →
<!-- mdv: section required -->## Example<!-- mdv: code required lang.in=[ts, sh] -->
$npx mdv check guide.mdexit 1
guide.md  State: what the document claims; clear it by doing the work or saying it is not done  3:4  error  Language "js" not allowed in "Example", allowed: ts, sh  code-lang-not-allowed              help: Write the example in an allowed language; never relabel the fence to pass ✖ 1 problem (1 error, 0 warnings)  mdv explain <rule-id> for details
Try it in the playground ↗

PR title must be a conventional commit

.github/pull_request_template.md
<!-- mdv: document title title.pattern=/^(feat|fix|docs|chore)(\(.+\))?: .+/ -->
$gh pr view --json body -q .body | npx mdv check - --pr --title "Updated stuff"
  • ✗error: Title "Updated stuff" is not text matching /^(feat|fix|docs|chore)(\(.+\))?: .+/value-not-given

The title isn't in the body, so it comes in with --title.

detailstitle →
<!-- mdv: document title title.pattern=/^(feat|fix|docs|chore)(\(.+\))?: .+/ -->
$gh pr view --json body -q .body | npx mdv check - --pr --title "Updated stuff"exit 1
<stdin>  State: what the document claims; clear it by doing the work or saying it is not done    error  Title "Updated stuff" is not text matching /^(feat|fix|docs…  value-not-given           help: Write the value that is true, or say the work is not done ✖ 1 problem (1 error, 0 warnings)  mdv explain <rule-id> for details

Changelog: newest version first, every one dated

CHANGELOG.mdv.md
<!-- mdv: document headings.level=2 headings.mask=["Unreleased", "<semver> - <date>"] headings.order=desc headings.order.compare=semver -->
  • ✗9error: "1.2.0" gives no <date>value-not-given
detailsChangelog recipe →
<!-- mdv: document headings.level=2 headings.mask=["Unreleased", "<semver> - <date>"] headings.order=desc headings.order.compare=semver -->
$npx mdv check CHANGELOG.mdexit 1
CHANGELOG.md  State: what the document claims; clear it by doing the work or saying it is not done  9  error  "1.2.0" gives no <date>  value-not-given            help: Write the value that is true, or say the work is not done ✖ 1 problem (1 error, 0 warnings)  mdv explain <rule-id> for details
Try it in the playground ↗

Write a rule once, use it in every contract, and keep contracts where you like.

Same prose rules in every contract

shared/prose.mdv.md
<!-- mdv: define prose required content.words.min=12 content.none=@placeholders -->
rfc.mdv.md
<!-- mdv: include ./shared/prose.mdv.md --><!-- mdv: section use=prose -->## Summary<!-- mdv: section use=prose content.words.min=30 -->## Design
  • ✗3:1error: Placeholder "TBD" left in "Summary"placeholder-text
  • ⚠3warning: "Summary" has 1 word, needs at least 12too-few-words
  • ⚠7warning: "Design" has 3 words, needs at least 30too-few-words

include of a file brings its defines; a rule written next to the set wins.

detailsSpec: rule sets →
<!-- mdv: include ./shared/prose.mdv.md --><!-- mdv: section use=prose -->## Summary<!-- mdv: section use=prose content.words.min=30 -->## Design
$npx mdv check rfc.mdexit 1
rfc.md  State: what the document claims; clear it by doing the work or saying it is not done  3:1  error    Placeholder "TBD" left in "Summary"      placeholder-text                help: Write the real content, or "Not specified." if unknown    3  warning  "Summary" has 1 word, needs at least 12  too-few-words                help: Add the missing specifics, not filler    7  warning  "Design" has 3 words, needs at least 30  too-few-words                help: Add the missing specifics, not filler ✖ 3 problems (1 error, 2 warnings)  mdv explain <rule-id> for details

One base contract, one section changed per team

docs/_template.mdv.md
<!-- mdv: document extends=./base.mdv.md --><!-- mdv: section id=examples override required -->## Examples<!-- mdv: code required lang.eq=sh -->
  • ✗1error: Missing section "Examples"missing-section

override replaces the parent's block with the same id=.

detailsOverrides →
<!-- mdv: document extends=./base.mdv.md --><!-- mdv: section id=examples override required -->## Examples<!-- mdv: code required lang.eq=sh -->
$npx mdv check docsexit 1
docs/retries.md  Shape: fix the form; this does not change what the document claims  1  error  Missing section "Examples"  missing-section            help: Add or rename a heading to "## Examples" ✖ 1 problem (1 error, 0 warnings)  mdv explain <rule-id> for details

Share one Security section across every contract

rfc.mdv.md
<!-- mdv: section required -->## Summary<!-- mdv: include ./shared/security.mdv.md#security -->
shared/security.mdv.md
<!-- mdv: section required -->## Security<!-- mdv: list required items.all=done-or-na -->- [ ] Threat model reviewed
  • ✗1error: Missing section "Security"missing-section

#slug picks one section; without it, every section comes in.

detailsSpec: include →
<!-- mdv: section required -->## Summary<!-- mdv: include ./shared/security.mdv.md#security -->
$npx mdv check rfc.mdexit 1
rfc.md  Shape: fix the form; this does not change what the document claims  1  error  Missing section "Security"  missing-section            help: Add or rename a heading to "## Security" ✖ 1 problem (1 error, 0 warnings)  mdv explain <rule-id> for details

Keep contracts out of the docs folder

.mediva/contracts/docs/setup.mdv.md
<!-- mdv: section required -->## Install
$npx mdv check
  • ✗1error: Missing section "Install"missing-section

.mediva/contracts/<path>.mdv.md checks the document at <path>.

detailsCLI › zero-config scan →
<!-- mdv: section required -->## Install
$npx mdv checkexit 1
docs/setup.md  Shape: fix the form; this does not change what the document claims  1  error  Missing section "Install"  missing-section            help: Add or rename a heading to "## Install" ✖ 1 problem (1 error, 0 warnings)  mdv explain <rule-id> for details
Try it in the playground ↗

alsomdv init skillmdv check x.mdv.md

Decide what blocks, what warns, and what CI looks at.

Relax a rule for one folder of drafts

mediva.config.mjs
export default {  overrides: [    { files: ["docs/drafts/**"], rules: { "missing-section": "warn" } },  ],};
without it
  • ✗1error: Missing section "Decision"missing-section
with it
  • ⚠1warning: Missing section "Decision"missing-section

Config names a rule by the code mdv check prints.

detailsCLI › rules and overrides →
## Summary Maybe retry failed webhooks with backoff.
$npx mdv checkexit 1
docs/drafts/retries.md  Shape: fix the form; this does not change what the document claims  1  error  Missing section "Decision"  missing-section            help: Add or rename a heading to "## Decision" ✖ 1 problem (1 error, 0 warnings)  mdv explain <rule-id> for details

also-f githuberror=too-few-words"too-few-words": "off"--fail-on warning-f jsonmaxSeverity=warn

Only check the documents this change touched

$npx mdv check --new
  • ✗3:1error: Placeholder "TODO" left in "Summary"placeholder-text
  • ✗5error: "Rollback" is emptyempty-section

Picks the documents changed since the merge base with the default branch; each is checked in full.

detailsCLI › only what a change touched →
## Summary TODO ## Rollback
$npx mdv check --newexit 1
docs/retries.md  State: what the document claims; clear it by doing the work or saying it is not done  3:1  error  Placeholder "TODO" left in "Summary"  placeholder-text              help: Write the real content, or "Not specified." if unknown    5  error  "Rollback" is empty                   empty-section              help: Write the section's content, or "Not specified." if there is none ✖ 2 problems (2 errors, 0 warnings)  mdv explain <rule-id> for details

Pre-commit: check only what you are committing

$npx mdv check --staged
  • ✗3:1error: Placeholder "TBD" left in "Summary"placeholder-text

--staged reads the git index, so unstaged edits never count.

detailsPre-commit recipe →
## Summary TBD
$npx mdv check --stagedexit 1
notes/a.md  State: what the document claims; clear it by doing the work or saying it is not done  3:1  error  Placeholder "TBD" left in "Summary"  placeholder-text              help: Write the real content, or "Not specified." if unknown ✖ 1 problem (1 error, 0 warnings)  mdv explain <rule-id> for details

Start here. write an X.mdv.md contract beside X.md (ready-made starters are being rebuilt). Then run npx mdv check, and add the GitHub Action to check every pull request.

Not on this page, and in the reference instead: grounding sources (--source, mdv sources) · the other @tables/* and @numbers/* checks · content.sentences.* · custom empty states (empty=) · custom codes (code=, message=) · mask fragments · plugins · editor and LSP setup. Everything else: recipes · CLI · rule reference · the spec.