mds-lang
v1.0.1
Published
MarkDown Superior - a deterministic, addressable, token-efficient markup language for humans and AI agents
Maintainers
Readme
title: MDS - MarkDown Superior description: A deterministic, addressable, token-efficient markup language for humans and AI agents version: 1.0.0
MDS - MarkDown Superior
Markdown won because it reads well as plain text. It aged badly because it was specified by its implementation: emphasis rules nobody can recite, invisible line breaks, indented code blocks that fire by accident, and a table syntax that spends a third of its bytes on a row of dashes.
MDS keeps what made Markdown win and fixes what made it unreliable - then adds the two things documents need now that machines read them too: stable addresses and structured data.
Files use the .mds extension.
Why
For people
- One way to write each construct - a formatter never surprises you
snake_caseand__dunder__are never mangled- No invisible syntax: line breaks are a visible
\ - Semantic callouts, steps, tabs, cards and grids without hand-written HTML
- Syntax-highlighted code with line numbers, built in and dependency-free
- A default stylesheet with real typography, dark mode and print styles
For agents
- Every section has a stable address (
§2.1) to cite and re-fetch - Read a cheap outline, then fetch only the section you need
- Structural selectors and text search that return addresses, not documents
- Facts live in typed data blocks, already parsed - no re-extraction
- Block-level diffs: a reworded paragraph is one change, not six
- A total parser: malformed input yields diagnostics, never exceptions
MDS Studio - editorul pentru oameni non-tehnici
Repo-ul include o aplicatie de scris MDS fara sa stii sintaxa: scrii direct in pagina, exact cum va arata documentul. Sintaxa nu apare nicaieri decat daca o ceri tu, din butonul „Sursa”.

Aplicatia nativa (fereastra proprie, meniuri de sistem, fisiere reale):
npm run build # o singura data
npm install --prefix desktop # o singura data
npm run desktop # porneste aplicatia
bash desktop/install-launcher.sh # optional: o pune in meniul de aplicatii
npm run package --prefix desktop # optional: pachet real - dist/linux-unpacked/mds-studio
npm run dist --prefix desktop # optional: pachet portabil - dist/MDS Studio-*.AppImageUltimele doua comenzi produc un binar real (via electron-builder), nu doar
scriptul de dezvoltare electron . - orice integrare externa ("deschide in
MDS Studio") poate referentia acest artefact in loc sa presupuna calea de pe
masina de dezvoltare.
Fisier > Deschide/Salveaza folosesc dialogurile sistemului; titlul ferestrei
arata documentul curent, iar ● inseamna modificari nesalvate - la inchidere
aplicatia intreaba ce faci cu ele. Un .md primit pe mail se deschide direct
si e convertit automat la MDS curat. mds-studio fisier.mds deschide fisierul.
Exista si varianta in browser (acelasi editor, fara instalare):
npm run app # -> http://localhost:8765 (sau PORT=... npm run app)| Ce ofera | Cum |
| --- | --- |
| Mod scriere | scrii in documentul randat; Enter face un bloc nou, fara sintaxa |
| Adaugare | un singur buton +, sau tasta / pe un rand gol: cautare si categorii |
| Formatare | bara mica ce apare pe selectie: B, I, evidentiat, taiat, cod, link |
| Drag & drop | muti blocuri de manerul din stanga; tragi elemente noi din meniu |
| Ecran de start | modele de pornire si documentele recente, deschise dintr-un click |
| Sursa MDS | un click (Ctrl+E) pentru vederea alaturata cu textul brut |
| Cuprins si verificari | discrete in bara de jos; click pe o problema sare la randul ei |
| Deschide .md vechi | convertit automat la MDS curat, fara avertismente |
| Export | .mds formatat, pagina web, Markdown clasic, text simplu |
| Salvare | automata in browser; Ctrl+S scrie fisierul real de pe disc |
Documentul randat sta intr-un shadow root: stilurile temei nu ating aplicatia,
iar evenimentele trec, ceea ce face posibile drag & drop-ul si scrisul direct in
pagina. Continutul e igienizat de renderer (HTML brut escapat, atribute on*
scoase, javascript: neutralizat), iar CSP-ul paginii interzice oricum orice
script inline si orice resursa externa.
Install
npm install mds-lang[!NOTE] Not on the npm registry yet
mds-langis not published (npm view mds-lang version404s). Publishing is a one-way door - it needs the maintainer's npm credentials and, once a name is taken, cannot be un-taken - so it stays a manual step for a human to run, not something a tool does on its own initiative. Until then, install straight from this repository:npm install github:horiastanxd/MDS#main # or a tag, once one is cut # or, without git: git clone https://github.com/horiastanxd/MDS.git && cd MDS && npm install && npm run build && npm link
npm pack --dry-runsucceeds cleanly today (verified: 116 files, ~154 kB tarball) - the package itself is ready the moment someone runsnpm publish.
A taste
---
title: Payments Runbook
owner: platform
---
# Payments Runbook
:::warn "One-way door"
This release drops a column. Snapshot the database first.
:::
|= Stage | Owner | Timeout {align=llr}
| Snapshot | platform | 10m
| Migrate | data | 20m
| Deploy | platform | 5m
:::data {format=csv}
metric,threshold,window
error rate,0.5,5m
p99 latency,400,5m
:::
Press :kbd[Ctrl+C] to abort. See [[incident-response#rollback]].
// Reviewed quarterly.That table has no separator row. Those thresholds are a real array on the AST node, not prose an agent has to re-parse. The callout is a semantic container, not a blockquote convention. The comment never reaches any output but stays readable in the source.
The addressing model
Every heading gets a section address; every other block gets its section's address plus an ordinal.
$ mds outline runbook.mds
§1 Payments Service Runbook (~895t)
§1.1 Service facts (~43t)
§1.2 Deploying (~320t)
§1.3 Health thresholds (~83t)
§1.4 Common incidents (~320t)
§1.4.1 Elevated 5xx after deploy (~124t)
§1.4.2 Queue backlog (~88t)
§1.4.3 Database connection exhaustion (~102t)
§1.5 Escalation (~81t)
$ mds get runbook.mds '§1.4.2'
### Queue backlog
The consumer scales on queue depth, not CPU...[!TIP] This is the point
Answering "why is the queue backing up?" costs 117 tokens of outline plus 88 tokens of section - 205 instead of 935, a 78% saving, on a document that is only 2.8 KB. The gap widens with document size: the spec in this repository is 4072 tokens, and its outline is 351.
Built for AI agents
MDS documents are agent-ready without a second file format:
:::rule {#no-force must scope=git}
Never force-push to main.
:::
:::ai
Run `npm run check` before committing.
:::
:::human
Long onboarding story - the agent never pays tokens for this.
:::mds rules ghid.mds # every rule, with citable ids
mds context ghid.mds --budget 2000 # the AI view, packed into a budget
mds agents ghid.mds --flavor claude # generate CLAUDE.md
mds agents ghid.mds --flavor codex # generate AGENTS.mdmds context is the economy engine: front matter and rules always included,
sections packed by priority, and everything elided leaves a stub with its
address, size and fetch command - the agent gets a map of what it did not
receive. One .mds is the single source of truth for the human handbook, the
CLAUDE.md, and the budget-fitted context.
Machine callers: capabilities, prompts, a persistent process
A harness that shells out to mds - a bridge process, an editor plugin, a
build step - needs three things a text-only CLI does not naturally give it: a
way to ask what this binary supports before relying on it, its own copy of the
syntax it should teach an agent to write, and a way to avoid paying a fresh
Node cold start on every single call.
mds doctor # {name, version, spec, node, capabilities:[...]}
mds compact doc.mds --json # {output, tokensBefore, tokensAfter, tokensSaved, bytesBefore, bytesAfter}
mds terse doc.mds --json # same shape
mds context doc.mds --budget 2000 --json
mds instructions --json # {syntaxPrimer, denseFormatRules, denseDecodeHint, outputInstruction, denseOutputInstruction}
mds agents doc.mds --flavor forge # like --flavor claude, plus the syntax primer appendedmds doctor (and mds --version --json, the same manifest) answers with
stable capability strings - compact.json, terse.json, context.json,
agents.flavor.forge, frontmatter.toml, doctor, instructions, serve -
so a caller checks once what an installed mds supports instead of probing a
flag and reading an exit code to find out.
mds instructions is the single source of truth for the prompt blocks a
harness injects when it asks an agent to write MDS: the syntax primer, the
dense record format for files meant for another agent rather than a person,
and the "write your result to this file" block in either shape
(--output-file <path>, --dense for the record form). A harness that hand-copies
this text into its own source drifts out of sync with the parser silently the
first time the syntax changes; one that shells out to this command cannot.
Front matter also accepts +++ (TOML) as well as --- (YAML): the fields
land in the same meta map either way, and every serialiser re-emits the
delimiter a document was parsed with, so a +++-fronted document round-trips
losslessly through fmt/compact without being silently relabelled as YAML.
This exists for metadata that already lives in TOML - Forge's memory pages,
Hugo pages - not as a second way to write new MDS front matter.
mds serve runs one persistent process instead of a new one per call:
newline-delimited JSON in on stdin, one response per line out on stdout,
id pairs a response back to its request. It exists because a caller with
many small documents or a live-typing preview pays a full Node start for each
one otherwise - measured at ~27x fewer milliseconds per request against
30 cold mds compact calls on this machine (npm run bench:serve; the
number is a relative comparison on one machine, not a portability claim).
It speaks stdio, not a socket - a caller already owns the child process it
spawned, so there is no port or path to negotiate and nothing listening on
the network to secure.
$ echo '{"id":1,"method":"compact","params":{"source":"# Hi\n"}}' | mds serve
{"id":1,"result":{"output":"# Hi\n","tokensBefore":3,"tokensAfter":3,"tokensSaved":0,"bytesBefore":6,"bytesAfter":6}}Methods: ping, doctor, compact, terse, fmt, md, context, lint,
exit. Malformed input on a line, or an unknown method, returns {id, error}
on that one response and keeps the process running for the next line.
Measured savings
Numbers from the test suite, using a token estimator calibrated against BPE tokenisers. They are relative comparisons, not exact billing figures.
| Comparison | Saving | | --- | ---: | | Table vs the GFM equivalent | 20-24% | | Data block vs the table it replaces | 15% | | Callout vs a blockquote alert | 17% | | Outline + one section vs the whole document | 78% | | Whole prose-heavy document vs GFM | 7% |
Prose is prose - MDS does not compress English. The savings are on structure and on not reading what you do not need.
Performance
The parser is single-pass and line-oriented, with no backtracking and no regular-expression scanning of whole documents. On a laptop it parses about 16 MB/s, or 12 MB/s including HTML rendering. The test suite guards the shape of that curve rather than the number: it asserts that quadrupling the input roughly quadruples the time, which is the regression a hand-written parser actually suffers.
CLI
mds html doc.mds # HTML fragment
mds page doc.mds # standalone page with the default theme
mds md doc.mds # downgrade to GitHub Flavored Markdown
mds text doc.mds # plain text
mds fmt doc.mds --write # canonical form for humans
mds fmt docs/*.mds --check # CI gate: exit 1 if anything is unformatted
mds compact doc.mds # minimal form for models
mds json doc.mds --pretty # the AST
mds outline doc.mds # addresses and token estimates
mds get doc.mds '§2.1' # extract one section
mds find doc.mds 'container[name=warn]' # select blocks structurally
mds search doc.mds 'rollback' # find text, get the section it is in
mds bundle doc.mds # expand every ::include into one file
mds diff old.mds new.mds # block-level structural diff
mds lint doc.mds # diagnostics, exit 1 on errors
mds fix doc.mds --write # repair what can be repaired
mds upgrade legacy.md --write # markdown to canonical MDS
mds stats doc.mds # size across every representation
mds instructions --output-file OUT.md # prompt blocks that teach an agent MDS
mds doctor # version + capability manifest, as JSON
mds serve # persistent process, newline-JSON on stdioUse - as the filename to read stdin.
Finding things without reading the document
$ mds find runbook.mds 'container[name=warn]'
§1.2/1 container One-way door This release drops the legacy_txn_ref column…
$ mds search runbook.mds snapshot
§1.2 Deploying
This release drops the legacy_txn_ref column. Take a snapshot before step 2…
$ mds get runbook.mds '§1.2' --format textSelectors are structural: heading[depth=2], code[lang=ts],
container[name=steps] code, paragraph:contains(rollback), or several
separated by commas. Every result carries an address, so find and search
feed straight into get.
Composing documents
# handbook.mds
::include(parts/intro.mds) {shift=auto}
::include(api.mds#authentication)mds bundle expands includes into one ordinary MDS document. shift=auto nests
a fragment under whatever section pulled it in, so the same fragment works at
any depth. Cycles and missing targets are diagnostics, not crashes.
Reviewing a change
$ mds diff v1.mds v2.mds
~ §1.2/2 paragraph (78% same)
- The deploy takes about ten minutes.
+ The deploy takes about fifteen minutes.
+ §1.4/1 container Note Check the queue depth first.
> §2.1 heading (100% same)
1 added, 0 removed, 1 changed, 1 movedRewrapping a paragraph is not a change. Rewording one is a single change with an address. A block that moved is reported as moved, not as a deletion beside an unrelated insertion.
API
import {
parse, render, renderPage, stringify,
outline, extract, query, search,
lint, fix, diff, resolveIncludes,
} from 'mds-lang';
const doc = parse(source);
render(source); // HTML fragment
renderPage(source); // full page, theme inlined
stringify(doc, { compact: true }); // model-facing text
outline(doc); // section tree with token estimates
extract(doc, '§2.1'); // one section's source
query(doc, 'container[name=warn]'); // structural selection
search(doc, 'rollback'); // text search with section context
diff(before, after); // block-level changes
lint(doc); // diagnostics
fix(source); // repaired source plus what changed
doc.meta; // parsed front matterCustom rendering plugs in without forking the renderer - a hook that returns
undefined falls through to the built-in behaviour:
render(source, {
hooks: {
container: (node, api) =>
node.name === 'chart'
? `<div class="chart" data-spec="${api.escapeAttr(api.blocks(node.children))}"></div>`
: undefined,
},
});Zero runtime dependencies. Pure functions - no filesystem, no network, no
globals. resolveIncludes takes a read callback rather than touching the
filesystem itself, so it works over any source of documents.
Guarantees
- Round-trip.
parse(fmt(doc))andparse(compact(doc))are structurally identical todoc, and both serialisations are idempotent. Enforced for every construct in the test suite. - Total parsing. No input throws. Malformed data blocks and unclosed fences become diagnostics.
- Legible truncation. A document cut off mid-construct - the shape a
streaming agent's output has at every point before its last token - renders
as that construct's element with whatever content arrived, never as
unrendered
:::/````` syntax sitting in the page as plain text. An unclosed fence becomes a code block; an unclosed container becomes its element with a partial body; a table missing trailing cells renders the rows it has; front matter without a closing delimiter falls back to body text instead of being dropped.test/truncation.test.tsfixes this per construct - useful for anything rendering an agent's output live, not just finished documents. - Safe by default. Raw HTML is escaped,
on*attributes are dropped,javascript:URLs are neutralised. Opt in withallowDangerousHtml. - Superset. CommonMark and GFM documents parse and mean the same thing.
- Verified fixes.
mds fixlints before and after, and reports only the diagnostics that actually disappeared.
Migrating from Markdown
mds upgrade README.md --out README.mdsThe upgrade rewrites setext headings, indented code blocks, two-space line breaks, underscore emphasis, GitHub alerts and GFM tables, then reformats to canonical MDS. It never touches the inside of a code fence.
mds lint flags the Markdown habits that are inert in MDS, so a partially
migrated document tells you what still needs attention.
What MDS removes
| Removed | Why | Replacement |
| --- | --- | --- |
| Setext headings | invisible, cannot nest | # headings |
| Indented code blocks | accidental indentation becomes code | fenced code |
| Two-space line breaks | invisible syntax | trailing \ |
| _underscore_ emphasis | corrupts snake_case | *asterisks* |
| Table separator rows | pure decoration | the \|= header marker |
| HTML as the extension point | unsafe, unstructured | containers and directives |
Repository
| Path | Contents |
| --- | --- |
| docs/SPEC.mds | the language specification, written in MDS (docs/SPEC.md/docs/spec.html are generated - see below) |
| src/ | the reference implementation |
| test/ | 545 tests: parsing, rendering, round-trips, retrieval, diffs, injection, truncation, serve, speed, and the app |
| conformance/ | a portable fixture suite for other implementations |
| examples/ | realistic documents, plus a showcase of every construct |
| app/ | MDS Studio: the editor (studio.js) plus the browser shell (npm run app) |
| desktop/ | MDS Studio Desktop: the same editor in a native window (npm run desktop); npm run package --prefix desktop builds a real installable artifact (dist/linux-unpacked/, dist/*.AppImage), not a dev-path assumption |
| editors/vscode/ | syntax highlighting and editing support |
| scripts/ | dev tooling: doc generation, conformance fixtures, benchmarks |
| experiments/ | prototypes not wired into the published build (native C/Rust accelerators) |
| .agents/ .vibe/ .forge/ | local agent-loop tooling for multiple agent workflows; not part of the published package |
README.md/index.html are generated from docs/README.mds; docs/SPEC.md/docs/spec.html
are generated from docs/SPEC.mds - run npm run docs after editing either source.
Run the suite with npm test, or npm run check for typecheck, tests and a
formatting gate together.
Implementing MDS elsewhere
conformance/suite.json is a single JSON file holding, for every case, the
source, the expected AST, the expected addresses, and the expected output of
each serialiser. It needs nothing from this toolchain to consume. See
conformance/README.md for what is normative and what is merely useful to diff
against.
License
MIT.
