mundimark/awesome-markdown 收录了一批想修补或取代 Markdown 的项目,GEML 自己也在它的「Beyond Markdown」一节里。这一页逐个看它们做了什么,和 GEML 在哪里撞车,在哪里走的是另一条路。清单外有几个项目离 GEML 更近,也一并放进来,单独标出。
结论。没有哪个项目同时做了 GEML 的三件事:一种带类型的块,每块有可寻址的 id,引用在构建时和每次写入前都被检查,出错就拦下。
单看其中一件,都有人做得比 GEML 早或比 GEML 深。通用块和属性是 Djot、Pandoc、MyST、Markdoc 的共识;带规则 id 的校验,Markdoc、mystmd、Carve 都有;按选择器查询和改写 Markdown,mq 做得比 geml get 更像一门语言。GEML 的不同在于把这三件事绑在一起,再加上按块写入的命令行、MCP 和块级历史。
功能上最接近的是 Carve,但它的影响力还很小。它和 GEML 在清单的同一节,今年 5 月才创建,已经有 lint、JSON AST、补丁式编辑、MCP 服务和 agent skill,推进得很快;但只有 32 个 star,也没有找到外部用户。结合影响力看,它是值得跟踪的项目,眼下不是主要对手。它和 GEML 的差别在三点:地址是标题 id 和 AST 路径,写入交给补丁确认,不兼容 Markdown。
思路相撞 同一个问题,相近的解法。部分重叠 在某一件事上同路。可借鉴 有 GEML 缺的东西。另一条路 目标或层次不同,或是 GEML 的底座。前四组来自清单,最后一组是清单外补充的。
| 项目 | 它是什么 | 和 GEML 的关系 | 判断 | |
|---|---|---|---|---|
| 新一代轻量标记语言 · 清单「Beyond Markdown」一节 | ||||
| 1 | Carve2026-05 · 32 star | 基于 Djot 的「后 Markdown」。有 EBNF 语法、2,200 个测试用例、JS/Rust/PHP 三套实现,还有 lint、JSON AST、补丁式编辑、MCP 服务和 agent skill。 | 同一节里重叠最多:检查坏引用、让 agent 选取和编辑都做了。地址是标题 id 或 AST 路径,不是任意块上作者写的 id;交叉引用只能指向标题和带编号的题注。 | 思路相撞 |
| 2 | Djot2022 · 2,046 star | John MacFarlane 为修 CommonMark 的解析难点而做,加了属性、通用容器、行内容器。Pandoc 从 3.1.12 起能读写它。 | 「通用块加属性」同路。没有块寻址,#id 引用不检查,没有嵌入语法,扩展只能写 AST 过滤器。 | 部分重叠 |
| 3 | VersaDok2025 · 17 star | kramdown 作者的新语言,<<< >>> 通用块,::名字: 扩展块可以接管原始正文。作者自称 alpha。 | 扩展块是四个里唯一接近「类型决定正文怎么读」的。没有检查,没有寻址。 | 部分重叠 |
| 4 | Texti2017 · 9 star | 把 Markdown、维基标记和 LaTeX 合在一起的提案,标题改成 == 标题 ==。 | 2017 年的草案,解析器是空模块,此后停更。 | 另一条路 |
| 老牌语法扩展 · 清单「Markdown Syntax Extensions」与「Libraries」 | ||||
| 5 | MultiMarkdown | 加了元数据、表格、脚注、交叉引用、整文件嵌入 {{file}}。第 7 版在预发布。 | 功能清单和 GEML 有交集,但每样都是一种特例语法。 | 另一条路 |
| 6 | Markdown Extra | PHP Markdown 的扩展:标题 {#id}、围栏代码、表格、定义列表、缩写。 | GEML 标题上的 {#id} 就是这一脉的写法。 | 另一条路 |
| 7 | Markdown Extended | 想把 Extra 和 MultiMarkdown 合成一份规范的 PHP 解析器。2024 年后没更新。 | 同上。 | 另一条路 |
| 8 | CriticMarkup | 在正文里写修订:增、删、替换、批注、高亮。 | GEML 有修订历史,但表达不了「还没被接受的建议」。 | 可借鉴 |
| 9 | kramdown · Pandoc | kramdown 的属性列表 {: #id};Pandoc 的 fenced div、JSON AST 和过滤器。 | 通用块和「文档先变成 AST」这条路的前辈。 | 部分重叠 |
| 规范化的方言 · 清单 CommonMark、GFM、VFMD 三节 | ||||
| 10 | CommonMark | 一份无歧义的 Markdown 规范和测试集。 | GEML 的散文部分从这里继承,是底座不是对手。 | 另一条路 |
| 11 | GFM | CommonMark 加表格、任务列表、删除线、自动链接。 | GEML 直接读 .md 时,在两套语法分歧的地方按 Markdown 的规则读,GitHub 的标题锚点也能作为链接目标。 | 另一条路 |
| 12 | VFMD | 另一份无歧义规范。 | 2016 年以后没有更新。 | 另一条路 |
| 工具 · 清单各节 | ||||
| 13 | mq2025 · 1,040 star | 用类似 jq 的语法查询和改写 Markdown,附 MCP 服务。 | 和 geml get/find/list 做的是同一件事。地址按选择器和标题文字推出,MCP 故意只读。 | 思路相撞 |
| 14 | darnlink2026-07 · 19 star | 用 UUID 锚住文件,文件搬家后自动修链接,可以当 CI 门。 | 文件级的引用检查与修复。GEML 能发现跨文档引用断了,但不会修。 | 可借鉴 |
| 15 | mdcode2024 · 28 star | 代码块和源文件双向同步,按 region 取片段。 | 和 code {src=} 同一件事,还能把改动推回源文件。 | 可借鉴 |
| 16 | markedpp | Markdown 预处理:目录、标题编号、!include。 | 嵌入的粗粒度版本,找不到文件时原样保留,不报错。 | 另一条路 |
| 17 | Slidev48,891 star | 在 Markdown 幻灯片里用 Vue 组件。2026-07 起自带 MCP,按页读写。 | 「按单元读写」的 MCP 同路。单元是页,地址是页码,插入一页后就变了。 | 部分重叠 |
| 18 | demarkus2026-02 · 16 star | 基于 QUIC 的 Markdown 协议与服务器,给 agent 当带版本的记忆。 | agent 读写和版本历史同路。按标题 slug 读一节,写入只能整篇。 | 部分重叠 |
| 19 | MarkWay | 让网站给 agent 返回 Markdown 而不是 HTML 的约定。 | 只有「Markdown 是给 agent 的格式」这个前提相同。没有代码。 | 另一条路 |
| 20 | mk-notes | 把 Markdown 单向同步到 Notion。 | 几乎不重叠,2025-12 以后停更。 | 另一条路 |
| 清单外补充 · 离 GEML 更近的几个 | ||||
| 21 | MarkdocStripe · 8,494 star | 文档里只写 tag 和属性,含义由宿主的 schema 定义,并逐项校验,错误有稳定 id。 | 设计上最接近 GEML:声明式、封闭、可校验。没有块寻址和写入命令。 | 部分重叠 |
| 22 | MyST | Jupyter Book 的 Markdown:directive 注册表、带标签的交叉引用。 | 通用块和规则 id 同路,坏引用只报警。 | 部分重叠 |
| 23 | Quarto | 基于 Pandoc 的科技出版系统,代码块可执行,图表有交叉引用。 | 计算文档的方向;GEML 只有表格里的计算列和汇总行。 | 部分重叠 |
| 24 | AsciiDoc | 带属性的块、include::、<<xref>>,一门完整的文档语言。 | 块和嵌入同路,坏引用默认不显示。 | 部分重叠 |
| 25 | MDX19,809 star | 在 Markdown 里写 JSX 组件。 | 文档即程序,和 GEML 的封闭词汇表是两条路。 | 另一条路 |
Markdown 的块是一个个特例:代码、引用、列表、表格各有各的规则。后来者几乎都收敛到同一个办法:一种通用容器,用属性区分用途。区别在于类型集合是开放还是封闭、id 有没有规则、块里的正文怎么解析。
{#warn .warning} ::: warning 别在生产库上跑这条命令。 :::
::: {#warn .warning} 别在生产库上跑这条命令。 :::
```{warning} :name: warn 别在生产库上跑这条命令。 ```
{% callout type="warning" #warn %} 别在生产库上跑这条命令。 {% /callout %}
=== note {#warn} 别在生产库上跑这条命令。 === === table {#cost format=csv header=1} Item,Cost Hosting,120 === === data {#vars format=json} {"owner": "docs"} ===
| 类型 | id | 正文 | |
|---|---|---|---|
| Djot | 开放,class 随便写 | 属性 #id,不强制 | 一律按 Djot 解析 |
| Carve | 提示类有固定名,其余变成 div | 属性 #id 或标题 slug | 一律按 Carve 解析 |
| VersaDok | ::名字: 扩展块由处理器定义 | 属性 #id | 扩展可以接管原始行 |
| Pandoc | 开放,交给过滤器 | 属性 #id,不强制 | 一律按 Markdown 解析 |
| MyST | 注册表,未知 directive 报警 | :name: 或 (label)= | 每个 directive 自定 |
| Markdoc | schema 声明,未知 tag 报错 | 属性 #id | 按 Markdoc 解析 |
| GEML | 规范列出,profile 扩展,未知类型报警 | {#id},重复即 error | 按类型:代码、表格、数据是原样文本,note 是散文 |
几乎每个变体都能给块起 id、链接过去。分歧在于:链接指向一个不存在的 id 时,工具说什么。
Djot / Pandoc:见 [预算](#budjet) MyST:见 {ref}`budjet` Quarto:见 @fig-budjet AsciiDoc:见 <<budjet>>
| 工具 | 找不到目标时 |
|---|---|
| Djot | 不检查 #id djot.js 只对找不到的引用式链接报警 |
| Pandoc | 不检查 照样输出一个断链 |
| Carve | lint carve lint 报 broken-crossref 并非零退出;交叉引用只能指向标题和带编号的题注 |
| mystmd | warning 规则 id referenceTargetResolves |
| Quarto | warning Unable to resolve crossref |
| Asciidoctor | info possible invalid reference,默认不显示 |
| Markdoc | 不管链接 只校验 tag、属性、变量,错误有稳定 id 如 attribute-undefined |
# 预算 {#budget}
托管费每月 120 元。
# 附录 {#appendix}
见 [[#budjet]]。
$ geml check refs.geml error: unresolved reference `#budjet` (line 7) 1 error(s) exit 1 $ geml set refs.geml '#budget' --in new.geml error: refused by an error the document ALREADY had, which this edit did not cause: unresolved reference `#budjet` …; not written — repair it first
改的是 #budget 一节,坏引用在 #appendix,写入照样被拒。诊断码 unresolved-reference 在规范附录 A 里,严重度是 error。写入者不会在一份已经坏掉的文档上继续叠改动。
tag-placement-invalid 这类)、mystmd(referenceTargetResolves)和 Carve(broken-crossref)都做了,思路和 GEML 附录 A 的诊断目录一致。Carve 的 lint 还会非零退出,可以当 CI 门,这一点和 geml check 一样。不写整篇、只取或只改一部分,是 GEML 命令行的核心。清单里有两个工具在做相邻的事:mq 用类似 jq 的语法查询和改写 Markdown,darnlink 用 UUID 锚住链接,文件挪走了也能修回来。
$ mq '.h(2)' README.md # 所有二级标题 $ mq '.code("rust")' README.md # 所有 rust 代码块 $ mq 'section::section("Installation")' README.md $ mq -U '…' README.md # 改写后把整篇打到 stdout
section("Installation") 就找不到了。-U 只输出到 stdout;写回文件要靠配套的 mq-mount 加 --write,或 mq-db 加 --write-back。写之前不做检查。$ geml list doc.geml #install heading h1 L1-8 安装 #install-before-upgrade prose L3-3 #upgrade heading h2 L5-8 升级 #usage heading h1 L9-12 用法 $ geml find 升级 . doc.geml #upgrade $ geml get doc.geml '#install' --head # 安装 {#install} $ geml rename doc.geml '#install' '#setup' # 安装 {#setup} 见 [[#setup]]。 # 引用一起改 $ geml list doc.geml --within '#install' #install-before-upgrade prose L3-3 #upgrade heading h2 L5-8 升级 $ geml get complex-form.geml '=== form-field {type=date}' 4 `form-field` blocks (L48-49 #founded · L99-101 #iso-expiry · …)
地址是作者写下的 #id,标题改名不影响它;没写 id 的段落也有一个由位置推出的地址,如 #install-before-upgrade。选择器还能按属性筛、用 --within 限定在一节里,最后一条取自仓库里 GEP-0008 的表单示例。每次写入先整份重新检查。
geml get/find/list 最近的工具,也同样有命令行和 MCP 两个入口。GEML 的选择器可以按类型、属性和内容地址筛,比如 === code {lang=rust}、{.warn},也可以用 --within 限定在某一节里;mq 多出的是管道和 map,以及 .h(1..3) 这样按标题层级选取。<!-- 目标文件的 frontmatter --> --- uuid: 7f3a1e2c-… --- <!-- 引用方 --> [设计文档](docs/design.md) <!-- uuid: 7f3a1e2c-… -->
$ darnlink check . # 0 干净 · 2 完整性问题 · 3 有未锚定的链接 $ darnlink . --write # 文件挪走后,按 UUID 把路径改回来
见 [[design.geml#arch]]。
| 发生了什么 | GEML | darnlink |
|---|---|---|
| 块的 id 改了名 | geml rename 改掉每一处引用 | 不管块 |
| 目标文件挪了位置 | error 报出来,但不会修 | 按 UUID 找回新路径并改写 |
| 当作 CI 门 | geml check 非零退出 | darnlink check 非零退出,附 pre-commit 钩子 |
geml 在根目录下按它找回搬走的文件。这件事今天在 GEML 里没有位置,值得进待办。把别处的内容放进文档,是最常见的扩展需求。清单里 mdcode、Slidev、markedpp、MultiMarkdown 各有一种写法,清单外的 AsciiDoc 也有。分歧有两处:引用的是行号还是名字,找不到时报不报错。
```go file=main_test.go region=example … ```
源文件里用 #region example 注释圈出一段。mdcode update 把源码拉进文档,mdcode extract 把文档里的改动推回源文件。
<<< @/snippets/demo.ts#region-name {2,3|5} --- src: ./other.md#2,5-7 按页码取另一份幻灯片 ---
!include (test.js lang=javascript) markedpp,找不到就原样留着 {{chapter2.md}} MultiMarkdown 整文件嵌入 include::part.adoc[tag=intro] AsciiDoc,按 tag 取一段
=== code {#add lang=js src=src/math.js#L1-3} === === code {#gone lang=js src=src/math.js#L40-45} === === embed {src=guide.geml#install part=intro} ===
$ geml check code.geml error: code source `src/math.js#L40-45`: the file has 7 line(s), so lines 40-45 no longer exist — the range is stale (line 6) 1 error(s)
embed 按 id 取另一份文档的一节,part= 选整节、标题、导语或正文。被嵌文档先按它自己的规则独立解析,引用也受检。
想在 Markdown 里放一张图表、一个表单、一段交互,有两条路。一条是让文档直接写组件代码,另一条是让文档只写声明,由渲染器按一份词汇表去解释。
import { Chart } from './Chart.jsx' # 季度营收 <Chart data={rows} type="bar" />
--- layout: two-cols --- # 季度营收 <BarChart :data="rows" />
=== table {#q format=csv header=1} Quarter,Revenue Q1,120 Q2,180 === === diagram {#rev format=geml-chart data=#q type=bar x=Quarter y=Revenue} ===
图表的数据是同一文档里的表格,按 #q 引用,引用受检。没有可执行代码,GEML 规范第 1 节写明「没有原始 HTML 的后门」。
「让 agent 只读写一部分,不整篇重写」在今年成了共识。Slidev 在 7 月加了 MCP,Carve 的 MCP 是 9 月建的,mq 和 demarkus 也各有一个。差别在三个地方:地址稳不稳,写之前查不查,改坏了能不能退回去。
| 服务 | 读的单位 | 怎么写 | 地址 | 写之前 | 历史 |
|---|---|---|---|---|---|
| Carve carve-mcp,14 个工具 | 标题、脚注、某类节点、AST 路径 | 生成带源码哈希的可撤销补丁,不直接写文件 | 标题 id 或 AST 路径 | 可调 lint;补丁在源码变了时作废 | 撤销补丁 |
| Slidev 内置,2026-07 | 一页幻灯片 | 改、插、删、移一页,写回 .md | 页码,插入一页后后面的都变 | 无 | 无 |
| demarkus 15 个工具 | 整篇,或按标题 slug 取一节;大文档先给提纲 | 整篇发布或追加,带预期版本,冲突时给合并候选 | 路径、内容哈希、标题 slug | 版本冲突检测 | 整篇,服务器端哈希链 |
| mq mq-mcp | 一节或一类节点 | 不开放写入 | 选择器、标题文字 | 不适用 | 无 |
| GEML 11 个工具 | 一块、一节、一个坐标 | 按块、按节直接写文件 | 作者写的 #id | 整份文档重新检查,坏了就拒;选择器带上内容地址时,内容变了就不写 | 按块,.gemlhistory |
=== note@<hex> {#id} 能做同样的校验,内容一变就选不中、不写;还缺的是 get --json 和 MCP 把这个哈希直接交给调用方。CriticMarkup 是清单「语法扩展」一节里唯一和修订有关的项目。
托管费每月 {~~120~>150~~} 元。
{>>按新报价改的<<}
{++含备份。++}
增 {++ ++}、删 {-- --}、替换 {~~ ~> ~~}、批注 {>> <<}、高亮 {== ==}。标记留在正文里,接受或拒绝之后才消失。
$ geml history save doc.geml -m "按新报价改" $ geml history get doc.geml --json [ { "id": "20260930T…", "summary": "按新报价改", "offset": 0, "current": true }, … ] $ geml revert doc.geml '#cost' --rev 20260930T…
历史在 .gemlhistory 边车里,按块回滚。正文保持干净,但「建议还没被接受」这种状态,GEML 表达不了。
四件事:什么是真正独有的,谁是要正视的对手,哪些东西值得借,哪些不必担心。
带类型的块、作者写下的稳定 id、写入前整份检查,这三件事单拿出来都有人做过,放在一起的只有 GEML。再加上按块写入的命令行和 MCP、块级历史,以及直接读现有 .md 的能力:Carve 和 Djot 都要求先把文档转换过去,GEML 不用。
同一节、同一套说法:lint、JSON AST、补丁式编辑、MCP、agent skill。它今年 5 月才创建,已有 64 个仓库、2,200 个测试用例、三套实现,9 月底一天几十个提交;但只有 32 个 star,没有找到外部用户。结合影响力看,它是要跟踪的项目,不是眼下的主要对手。它和 GEML 的差别在任意块的稳定地址、写入即检查、兼容现有 Markdown 这三点,讲 GEML 时这三点也该放在语法之前。
思路相撞的两个都小:Carve 32 个 star,mq 1,040 个。影响大的是 Slidev(48,891)、Pandoc(46,456)、MDX(19,809)和 Markdoc(8,494)。评估对 GEML 的影响时,以后者为准:Slidev 说明按单元读写的 MCP 已经进了主流工具;Markdoc 是讲 GEML 时最好的参照系。
行内元素的属性和 id(Djot、Carve);按名字引用源码片段(mdcode、Slidev 的 region,AsciiDoc 的 tag);把文档里的代码改动推回源文件(mdcode);文件搬家后修复跨文档引用(darnlink);待审的修改建议(CriticMarkup);把内容哈希交给调用方做写前校验(Carve、demarkus)。
CommonMark、GFM、VFMD 是 GEML 散文部分的来源;MultiMarkdown、Markdown Extra 这些老扩展是特例语法的集合,GEML 已经把它们收进一种块里。MDX 是「文档即程序」的另一条路。MarkWay 和 mk-notes 在协议和同步这一层,不在文档格式这一层。
get --json 和 MCP 返回内容哈希,配合选择器里的内容地址做写前校验;按 region 引用源码,补在 code {src=} 上。仓库数据取自 GitHub API,时间 2026-09-30。「最近推送」是仓库最后一次 push 的日期,不一定是最后一次功能提交。
| 项目 | 仓库 | star | 创建 | 最近推送 | 许可证 |
|---|---|---|---|---|---|
| Carve | markup-carve/carve | 32 | 2026-05-13 | 2026-09-30 | MIT |
| Djot | jgm/djot | 2,046 | 2022-07-11 | 2026-07-01 | MIT |
| VersaDok | gettalong/versadok | 17 | 2025-01-09 | 2026-07-05 | MIT |
| Texti | texti/texti.github.io | 9 | 2017-07-14 | 2018-08-04 | CC0 |
| MultiMarkdown 6 / 7 | fletcher/MultiMarkdown-7 | 678 / 56 | 2017-01-19 | 2026-09-22 | 见仓库 |
| Markdown Extra | michelf/php-markdown | 3,462 | 2010-06-06 | 2025-06-17 | 见仓库 |
| Markdown Extended | e-picas/markdown-extended | 38 | 2013-06-02 | 2024-03-02 | 见仓库 |
| CriticMarkup | CriticMarkup/CriticMarkup-toolkit | 849 | 2013-01-10 | 2021-03-04 | 见仓库 |
| kramdown | gettalong/kramdown | 1,782 | 2008-11-30 | 2026-01-31 | 见仓库 |
| Pandoc | jgm/pandoc | 46,456 | 2010-03-20 | 2026-09-29 | GPL-2.0 |
| CommonMark | commonmark/commonmark-spec | 5,149 | 2014-08-14 | 2026-04-27 | 见仓库 |
| GFM | github/cmark-gfm | 1,133 | 2016-12-01 | 2026-09-28 | 见仓库 |
| VFMD | vfmd/vfmd-spec | 32 | 2013-09-01 | 2016-10-29 | 未声明 |
| mq | harehare/mq | 1,040 | 2025-02-24 | 2026-09-30 | MIT |
| darnlink | txemi/darnlink | 19 | 2026-07-11 | 2026-09-27 | GPL-3.0 |
| mdcode | szkiba/mdcode | 28 | 2024-01-08 | 2026-02-23 | MIT |
| markedpp | commenthol/markedpp | 39 | 2014-09-01 | 2025-08-18 | MIT |
| Slidev | slidevjs/slidev | 48,891 | 2021-04-24 | 2026-09-16 | MIT |
| demarkus | latebit-io/demarkus | 16 | 2026-02-14 | 2026-09-30 | README 写 AGPL-3.0 |
| MarkWay | RaysunKR/MarkWay | 2 | 2026-03-31 | 2026-06-03 | MIT |
| mk-notes | Myastr0/mk-notes | 125 | 2025-01-29 | 2025-12-23 | MIT |
| Markdoc | markdoc/markdoc | 8,494 | 2022-05-09 | 2026-09-16 | MIT |
| MyST | jupyter-book/mystmd | 525 | 2020-07-21 | 2026-09-28 | MIT |
| Quarto | quarto-dev/quarto-cli | 6,033 | 2020-09-25 | 2026-09-30 | 见仓库 |
| AsciiDoc | asciidoctor/asciidoctor | 5,218 | 2012-06-01 | 2026-09-01 | 见仓库 |
| MDX | mdx-js/mdx | 19,809 | 2017-12-24 | 2026-09-29 | MIT |
| GEML 探针 | 覆盖 | 结果 | 页面里对应 |
|---|---|---|---|
| refs.geml · new.geml | 坏引用在另一节,改本节 | 1 error 写入被拒 | id 与引用检查 |
| code.geml · src/math.js | 行号范围取代码,范围过期 | 1 error 范围过期 | 嵌入与同步 |
| doc.geml · complex-form.geml | list、list --within、find、get --head、rename;属性过滤 | 0 诊断 | 查询与改写 |
| samples.geml | note、table、data、geml-chart 的写法 | 0 诊断 | 通用块 · 组件与扩展点 |