geml

GEML

GEML — General Expressive Markup Language

*English 中文*

One format, two readers.
People and AI agents now co-write the same document.
Legible for people; addressable, verifiable, and versioned for machines.

GEML is plain text — organized by one typed block for everything, remembered by a .gemlhistory sidecar.

npm CI GEML check code: MIT spec: CC BY 4.0

Try GEML in the Playground — edit on the left, rendered on the right, and the build verdict flips the moment a reference breaks. No install.


GEML is a markup language for structured documents. A .geml file reads as plain text, so you never need a renderer to read it. And instead of a separate mini-syntax for each kind of content, GEML puts everything on one construct: the typed block.

=== code {#hello lang=python}
print("hi")
===

Code is a block. So are tables, diagrams, math, callouts, and document metadata. The shape is the same every time, which makes the format easy to learn and hard to get wrong.

Why a new format now

Markdown was designed for documents that people hand-write and people read. Today the same documents are also written, edited, reviewed, and queried by AI agents and CI pipelines — and that shift asks three things of a format that Markdown was never built to give:

GEML is built around those three. The goal wasn’t to bolt “AI features” onto a document format. It was to pick a format that is both simpler for people and more dependable for machines.

What’s different about GEML

Plenty of formats do one or two of these. What’s unusual about GEML is that one plain-text format does all three:

  1. One primitive for every structured block. Code, tables, diagrams, math, callouts, metadata — all the same === type {…} typed block. One grammar to learn, one grammar to generate correctly: no per-feature syntax, no HTML fallback.
  2. References checked at build time. Put an #id on any block and reference it anywhere; a dangling reference or a broken cross-document link is a build error, not a silent 404. Automated edits can’t quietly rot.
  3. Self-contained version history. A sibling .gemlhistory file reconstructs any past revision and rolls the document back — offline, with no git and no service — and it’s plain text an agent can read to understand how the document evolved.

For a fuller side-by-side across Markdown, HTML, CommonMark, AsciiDoc, and Org-mode, see the format comparison.

The format in 5 minutes

Typed blocks

One shape, every type. A block is always === type {#id .class key=val}=== — only the type (and how its body is read) changes:

=== code {lang=python}
print("hi")
===

=== note {.intro}
Parsed prose with *emphasis* and a [[#budget]] reference.
===

=== meta
title = "Budget plan"
===

A run of = (three or more) opens a block; an equal-length run closes it; longer fences nest inside shorter ones. A block that carries an #id can also close with the labeled fence === #id — no fence-length counting, which makes long or nested blocks much harder to get wrong. The type decides how the body is read — raw (verbatim: code, diagram, math, table), flow (parsed prose with inline markup: note), or data (one key=val per line: meta) — and every block may carry an attribute object {#id .class key=val}, where a .class is a semantic label, never a styling hook. The full inline grammar (emphasis, links, [[#id]] auto-references, media, footnotes, inline $math$) is in the spec.

Tables — two bodies, one model

Write a table visually:

=== table {#budget caption="Annual cost"}
| Plan  | Months | Rate |
|-------|-------:|-----:|
| Basic |      1 |   30 |
| Pro   |      2 |   30 |
===

…or as data, with computed columns and a summary row:

=== table {#fy25 format=csv header=1 compute="FY [%.1f] = Q1 + Q2 + Q3 + Q4" summary="Segment = 'Total'; FY [%.1f] = sum(FY)"}
Segment,  Q1, Q2, Q3, Q4
Cloud,     8, 10, 12, 14
Platform,  5,  6,  7,  9
Services,  3,  4,  4,  5
===

Both forms describe the same model. The FY column and Total row are computed at build time:

Segment Q1 Q2 Q3 Q4 FY
Cloud 8 10 12 14 44.0
Platform 5 6 7 9 27.0
Services 3 4 4 5 16.0
Total         87.0

compute runs + - * / ( ) per row over columns; summary adds a foot row from the aggregates sum / avg / min / max / count (with arithmetic over them, e.g. weighted ratios); a trailing [printf] sets numeric display.

Tables can also pull their data from an external CSV via src="regions.csv".

Math

=== math {#gauss caption="Gaussian integral"}
\int_{-\infty}^{\infty} e^{-x^2} dx = \sqrt{\pi}
===
\[\int_{-\infty}^{\infty} e^{-x^2} dx = \sqrt{\pi}\]

Diagrams & charts — host a DSL, or chart a table

GEML never interprets a diagram body; it routes it to a pluggable renderer (an unknown format is a warning, body preserved):

=== diagram {#flow format=mermaid caption="Review flow"}
graph LR
  A[Draft] --> B{Review} -->|ok| C[Publish]
===
graph LR
  A[Draft] --> B{Review} -->|ok| C[Publish]

A diagram can also chart a table — single source of truth, with the column references checked at build time and no data copied:

=== diagram {format=geml-chart data=#fy25 type=bar x=Segment y=FY}
===

Drawn from the #fy25 table above:

xychart-beta
  title "FY by segment"
  x-axis [Cloud, Platform, Services]
  y-axis "FY"
  bar [44, 27, 16]

A gift for programmers — geml-code-graph

To really feel how powerful and flexible a single GEML primitive is, let’s try it on a code graph — a familiar but demanding case for programmers: your whole codebase’s call graph, written as GEML. geml codemap build lays the call graph out as a tree of GEML documents — every method an #id block, with #calls / #called-by edges both ways. The downstream chain (what a method calls) for troubleshooting, the upstream chain (who calls it) for the blast radius — all visible in a second;

The method graph of geml-parser/render.ts: hovering RenderCtx.inline lights up its whole caller chain while everything else dims; clicking a node opens its source right beside the graph

npm i -g @geml/geml             # needs Node 22+
geml codemap build              # --root defaults to . : detect languages -> index -> one merged graph in ./.geml-code-graph/
geml codemap serve              # opens your browser on the graph

TS/JS — zero setup: build fetches the scip indexer by itself. Java / C / Python / Go / Kotlin — one extra download, Joern: unzip its release package and pass that folder to build, e.g. --joern C:\joern\joern-cli (or put it on PATH and skip the flag). Mixed front-end + back-end repo — everything merges into one graph.

geml-code-graph is itself a diagram format — one line embeds it in any GEML document (=== diagram {format=geml-code-graph src=.geml-code-graph/index.geml} ===), and every code change auto-triggers a rebuild, so the graph never drifts. Scale is no obstacle: the graph is plain-text data tables — tens of thousands of files and hundreds of thousands of edges stay instant to open and query (pan across the whole thing and its dense, web-like symmetry is genuinely striking), and you can grep any method name to trace its call chain.

Next — get hands-on

  1. Install the browser extension, then open a raw .geml link and watch it render — the GEML spec itself (dogfood — the spec is a GEML document, rendered at scale), the showcase (a computed table, four charts, a Mermaid flow, and math), or playground/sample.geml for the interactive code-graph.
  2. Or write your own right now in the ▶ Playground — no install.
  3. Then read the full spec (EN / 中文) for the whole grammar.

Why this works for humans and AI

The same shape that makes GEML pleasant to read directly is what makes it reliable under automation:

Using GEML with an LLM

GEML is meant to be written and edited by models — precisely. To change one thing, an agent needn’t re-read and re-emit the whole document: it addresses a single block by id, then validates. The CLI is designed against one bar — can a single agent run a document’s whole life from the shell? — so its verbs aim to be complete (a verb for every step), ergonomic (few flags, pipeline-friendly I/O), and consistent (name a target #id and the content adopts it; every write is guarded).

npm i -g @geml/geml                 # installs the `geml` command
geml doc.geml                       # document-model JSON (default --to json)
geml doc.geml --to md|html|geml     # convert (geml notes.md -> GEML; -o writes a file)
geml get    doc.geml ['#id']        # list every id, or print ONE block (a heading id = its section)
geml set    doc.geml '#license' --in template.geml#mit   # replace a block, forking another (id adopts #license)
geml add    doc.geml --after '#intro' --in snippet.geml  # insert a fragment (keeps its own ids)
geml delete doc.geml '#draft' '#tmp'           # remove one or more blocks
geml rename doc.geml '#old' '#new'             # rename an id + every reference to it
geml revert doc.geml '#plan' --rev -1          # roll ONE block back to an earlier revision

One entry, geml <file> [--to <fmt>], converts in either direction: the input format is inferred (--from overrides > extension > GEML) and the target is --to (default: a GEML input → JSON, a Markdown input → GEML). To mutate, four verbs cover the whole block lifecycle: set replaces a block (forking content from a file or stdin and adopting the target id), add inserts a fragment at a position, delete removes one or more blocks, and rename rewrites an id and every reference to it. Each mutation writes the whole updated document — in place for a file, to stdout for -, -o to redirect — so edits pipe cleanly, and each is guarded: re-parsed and refused if it would break the document. Read-and-patch by id keeps every edit small and precise — a fraction of the tokens of shipping the whole file.

GEML primer. Write the document as GEML. Every block is === type {#id .class key=val}===; the closing fence is a run of = of the exact opening length, and a longer fence nests a shorter one — or, when the block has an #id, close it with the labeled fence === #id (no length counting; prefer this for long or nested blocks). Block types: code/diagram/math/table (verbatim body), note (prose with inline markup), meta (one key=val per line). Headings are ATX # only — no --- frontmatter (use === meta). Every #id is unique and every reference ([[#id]], [text](#id), [^id], chart data=#id) must resolve. No raw HTML. Inline: *em*, **strong**, `code`, $math$, [text](url). The normative spec is GEML-spec.md.

MCP Server

This package includes a standard Model Context Protocol (MCP) server that exposes GEML document CRUD operations. It runs locally and supports Windows, macOS, and Linux.

To connect it to an MCP-compatible client, provide the npx execution command and specify the --root argument (the directory containing your .geml files).

Point --root at a repository that has a code graph (geml codemap build) and the same server also exposes the read-only call-graph tools — one entry, not two.

Claude Desktop

Add to your claude_desktop_config.json:

{
  "mcpServers": {
    "geml": {
      "command": "npx",
      "args": [
        "-y",
        "@geml/geml@latest",
        "mcp",
        "--root",
        "/absolute/path/to/your/docs"
      ]
    }
  }
}

Claude Code / CLI Clients

Run the following command to add the server:

/mcp add npx -y @geml/geml@latest mcp --root /absolute/path/to/your/docs

Ecosystem

Status, scope & contributing

GEML is 1.0 — stable, and used to write real documents (this repo’s own spec is one).

Maturity signals. A complete core spec (§1–§8) plus a history-extension spec, both EN / 中文; a working reference parser, renderer + CLI; a conformance suite (input → projected document model) that a second, independently-written parser must reproduce exactly — two separate implementations agreeing case-for-case is what keeps subtle rules like emphasis and lists from drifting — backed by 600+ unit and conformance checks (~99% line coverage; CI-gated at ≥95% lines / statements / functions / branches); and self-hostingGEML-spec.geml is the specification written in GEML, parsed clean on every test run.

Design boundaries (non-goals). GEML stays small on purpose:

Contributing. Contributions of every kind are welcome — bug reports, tooling and integrations, broader conformance coverage, and the spec itself. GEML is 1.0, but the format can still evolve: substantive spec changes are discussed and land through a GEP, each with its conformance case. The reference parser’s test suite is the contract, so code changes should keep npm test green and the dogfood spec parsing clean. The most valuable contribution is an independent parser in another language — a portable conformance suite makes it a weekend project; see docs/WRITING-A-PARSER.md.

Document English 中文
Core spec GEML-spec.md GEML-spec_CN.md
History extension GEML-history-spec.md GEML-history-spec_CN.md

Repository layout

spec/                  Core spec + .gemlhistory extension (EN / 中文), the dogfood
                       GEML-spec.geml, the CC-BY spec license, and proposals/ (GEPs)
geml-parser/           Reference parser, renderer, CLI + codemap toolkit (TypeScript, Node 22)
integrations/          Everywhere GEML plugs in: geml-viewer (browser extension),
                       geml-check-action (CI), vscode, obsidian, tree-sitter (brief)
playground/            In-browser playground (+ a live geml-code-graph of this repo)
docs/                  Guides, design notes, the format COMPARISON (EN / 中文),
                       assets, and an example .geml document to render

License & governance

Code (geml-parser/, integrations/geml-viewer/, integrations/geml-check-action/) is MIT (LICENSE). The specification documents are CC-BY-4.0 (LICENSE-spec.md) — a spec is not software, and anyone may build a conformant implementation. See GOVERNANCE.md for how decisions are made and CONTRIBUTING.md to get involved.