Mermaid 语法验证 Skill 实战指南:IB_Robot 文档 Mermaid 图的检查、修复与渲染验证
Mermaid 语法验证 Skill 实战指南:IB_Robot 文档 Mermaid 图的检查、修复与渲染验证
本文以 IB_Robot 仓库中 mermaid-syntax-validation 技能为蓝本,系统讲解如何对 Markdown/Sphinx 文档中的 Mermaid 图执行语法检查、仅限语法层面的最小修复,并证明生成后的 HTML 不再渲染 Mermaid 错误 SVG。读完本文,你将掌握一套「上下文检查 → 语法扫描与修复 → 真实构建 → 浏览器验证 → 结构化报告」的完整门禁流程,可直接用于该仓库 docs/ 目录及 DeepWiki 文档迁移产物的 Mermaid 图质量保障。
一、技能定位与适用场景
mermaid-syntax-validation 是 IB_Robot 仓库 .agents/skills 技能库中的文档类技能(分类为「文档」),在 AGENTS.md 中登记的功能描述为「Mermaid 图语法检查、修复与渲染验证」。它的定位是配合 deepwiki-config、deepwiki-translator、doc-link-validator 组成文档迁移与转换流水线——正如 docs/migration/README.md 所描述的,用户依次使用这 4 个技能即可完成 DeepWiki 文档的拆分、配置、翻译、生成与校验,而 Mermaid 检查是流水线的最后一环(docs/migration/README.md)。
该技能在以下场景下应当被触发:
- 生成后的文档页面显示
Syntax error in text、Parse error、Diagram error或mermaid version; - 用户要求检查
docs/下所有 Markdown Mermaid 图; - 用户要求在保留图内容的前提下修复 Mermaid 语法;
- Sphinx/MyST 文档流程使用
```mermaidfenced block,并依赖浏览器端 Mermaid 渲染; - 静态扫描通过,但渲染后的 HTML 仍出现 Mermaid 错误 SVG;
- 仓库使用本地 Mermaid runtime,例如
mermaid.min.js+mermaid-run.js,或mermaid.esm.min.mjs+chunks/这类本地 ESM runtime。
技能本体位于 .agents/skills/mermaid-syntax-validation/SKILL.md,其目录结构如下:
.agents/skills/mermaid-syntax-validation/
├── SKILL.md # 技能主文档:流程、命令与门禁规则
├── references/
│ ├── context-checks.md # 5 项上下文检查的原始定义与手工核对清单
│ └── syntax-scan-and-fix-rules.md # 高风险模式表与语法修复规则
└── scripts/
├── context_check.py # 上下文检查脚本(5 项检查一键执行)
├── fix_labels.py # 语法级 label 修复脚本(幂等)
├── html_scan.py # 构建产物 HTML 扫描脚本
└── parse_check.mjs # 基于 mermaid.parse() 的权威语法校验
二、核心原则:只修语法,不改语义
技能的工作边界是严格限定的,这是整套流程的第一条红线:
只改 Mermaid 语法。必须保留图的含义、节点文本、边含义、顺序和周围正文。
因此,执行修复时不要重写架构、重命名概念实体、简化图,或为了让解析通过而删除 label。如果可见文本必须转义(例如 label 内含冒号、括号等特殊字符),应使用 Mermaid 兼容语法并保留显示含义。这一原则在 docs/migration/README.md 的流程说明中也有对应描述:「只修 Mermaid 语法,不改变图含义、节点文本、边含义、顺序或周边正文」。
三、必做上下文检查:编辑前的 5 项门禁
在编辑任何 Markdown 文件之前,必须先运行技能脚本完成 5 项上下文检查(文档栈、本地 runtime 资源、fence 统计、含图 HTML 页面、script 标签/CDN 引用)。命令中的 <skill_dir> 指本技能目录(即 scripts/、references/ 的父目录,也就是 .agents/skills/mermaid-syntax-validation/),执行时替换为实际路径;--docs-root 等目标路径参数以当前工作目录为基准:
python -X utf8 <skill_dir>/scripts/context_check.py --docs-root docs [--build-dir docs/build/html]
该脚本对应 references/context-checks.md 中定义的 5 项检查,scripts/context_check.py 的 docstring 将其汇总为:
- 文档栈:定位
conf.py,报告extensions(是否包含myst_parser/sphinxcontrib.mermaid)、myst_fence_as_directive、mermaid_*系列配置项与html_js_files中加载浏览器 runtime 的条目; - 本地 runtime 资源:在
_static/(含_static/js/)下查找mermaid.min.js、mermaid-run.js、mermaid.esm.min.mjs、mermaid-init.js等 UMD/ESM runtime 文件,以及 ESM 必需的chunks/目录树; - fence 统计:统计
docs/根下所有*.md中```mermaidfenced block 的数量; - 含图 HTML 页面:在构建产物中找出包含
class="mermaid"容器的页面; - script 标签/CDN 引用:检查生成 HTML 中引用的 Mermaid runtime 脚本,以及是否泄漏了
cdn.jsdelivr.net、unpkg.com等在线 CDN 引用。
与之配套的门禁规则:
- 没有脚本输出,不得开始编辑任何 Markdown 文件;
- 最终报告的「已检查的 Mermaid runtime 文件」「docs 构建结果」「浏览器验证统计」等字段必须取自脚本输出或其后的构建/浏览器验证,不得凭记忆或推测填写;
- 脚本报告
WARNING(如 CDN 引用),或存在脚本未覆盖的人工判断项(conf.py monkey patch / override 语义、Sphinx 配置组合)时,必须阅读 references/context-checks.md 并按其规则处理,在报告中明确列出并说明处置方式。
在脚本不可用(如构建目录缺失)时,references/context-checks.md 还提供了逐项手工执行的 Python/PowerShell 等价命令,例如 fence 统计(Linux/bash 主路径):
python3 - <<'PY'
from pathlib import Path
print(sum(1 for path in Path("docs").rglob("*.md") for line in path.read_text(encoding="utf-8").splitlines() if line.strip() == "```mermaid"))
PY
四、Runtime 预期:Mermaid v10+ 的正确调用方式
技能对「当前本地 runtime 方案」的预期是:项目 runner(例如 mermaid-run.js)应当:
- 等待
window.mermaid存在; - 调用
window.mermaid.initialize({ startOnLoad: false, ... }); - 在页面加载后调用
window.mermaid.run(); - 绑定项目自定义交互,例如点击放大 modal。
一个关键版本判断:当前 Mermaid 版本应使用 mermaid.run(),把 mermaid.init() 示例视为旧写法,因为 Mermaid v10+ 已废弃该 API。
在验证手段的强弱上,技能给出明确排序:如果存在可运行的 Mermaid JS 环境,语法级检查优先使用 mermaid.parse(text, { suppressErrors: true })。静态 grep 适合初筛,但 parse() 和浏览器渲染是更强的证据——因为 parse() 与浏览器渲染使用同一解析器,复验全绿即等价于渲染通过。
五、语法风险扫描与修复:四步循环
编辑前必须完成「诊断 → 预览 → 修复 → 复验」循环,其顺序固定为 1→4,且该循环是幂等的(仅改语法;parse() 与浏览器渲染使用同一解析器,复验全绿即等价于渲染通过):
node <skill_dir>/scripts/parse_check.mjs <target_dir> # 1. 诊断:定位失败块
python -X utf8 <skill_dir>/scripts/fix_labels.py <target_dir> --dry-run # 2. 预览将修改的内容
python -X utf8 <skill_dir>/scripts/fix_labels.py <target_dir> # 3. 应用自动修复(graph/flowchart 标签语法)
node <skill_dir>/scripts/parse_check.mjs <target_dir> # 4. 复验:必须全绿
依赖说明:脚本依赖 mermaid、jsdom 从当前工作目录解析,缺失时必须站在目标目录之外(如仓库根)执行 npm install mermaid jsdom——npm 会在执行目录创建 node_modules,站在目标内执行会给目标目录新增文件。
parse_check.mjs 的实现要点(scripts/parse_check.mjs)包括:使用 jsdom 构造 DOM 环境并注册 window/document/navigator 全局对象;通过 mermaid.initialize({ startOnLoad: false, suppressErrorRendering: true }) 初始化;递归遍历目标目录下所有 *.md,用正则 /```mermaid\r?\n(.*?)```/gs 提取每个 fenced block 并逐一调用 mermaid.parse();全部通过时退出码为 0,否则为 1。
fix_labels.py 的自动修复范围(scripts/fix_labels.py)仅覆盖 graph/flowchart 图,sequence/state/class 图跳过:
- 标签引号翻倍(DeepWiki 导出/翻译引入,如
A[""x""]→A["x"]); - 标签内部引号转义(
A[a "b"]→A["a #quot;b#quot;"]); - 含特殊字符但未加引号的标签(
A[profile: {d: 0}]→A["profile: {d: 0}"]); - 匿名节点分配 ID(
["label"]→mmd_anon_N["label"]); - 边标签特殊字符加引号(
-->|Returns X[]|→-->|"Returns X[]"|)。
其中 #quot; 是 Mermaid 的 HTML entity 形式,用于在保留可见文本(显示为 ")的前提下转义内部引号。
高风险模式表
当 fix_labels.py 应用后 parse_check.mjs 仍有失败块时,禁止凭经验直接手工编辑,必须先阅读 references/syntax-scan-and-fix-rules.md,按下表逐行定位失败模式并按其「仅语法修复」列处理:
| 模式 | 失败原因 | 仅语法修复 | 自动修复 |
|---|---|---|---|
A --> ["label"] |
匿名节点作为边端点 | 创建稳定节点 id:A --> node_id["label"] |
手工(行中括号不处理,避免误伤边标签内括号) |
["label"] 独立声明 |
匿名节点声明 | 创建稳定节点 id:node_id["label"] |
fix_labels.py(分配 mmd_anon_N) |
subgraph "ID" ["Title"] |
subgraph 写法不兼容 | subgraph ID["Title"] |
手工(subgraph 语句行跳过) |
A -- "label" --> B |
旧式边 label 可能失败 | `A --> | "label" |
A --> B : "label" |
flowchart 旧式尾随 label | `A --> | "label" |
| `A --> | /topic (type) | B` | edge label 含未加引号的括号 |
B{func(arg)} |
diamond 节点 label 含未加引号的括号 | B{"func(arg)"} |
fix_labels.py |
A --> B: file.py:1-2 in stateDiagram-v2 |
transition label 内含额外冒号 | A --> B: file.py#58;1-2 |
手工(非 graph/flowchart,脚本跳过) |
A[""label""] |
标签引号翻倍(DeepWiki 导出/翻译引入),空标签边界 | 去重外层引号并转义内部引号:A["label"] |
fix_labels.py |
A[k: {v}] |
方括号标签内未加引号的 { 被解析为菱形开始 |
A["k: {v}"] |
fix_labels.py |
| `A --> | Returns X[] | B` | edge label 内未加引号的 [] 触发节点形状解析 |
注意:静态 grep 只能初筛部分模式,[]、{}、"" 翻倍这类问题只有 parse() 能稳定暴露,因此修复循环必须以 parse_check.mjs 为准。
无 JS runtime 时的静态初筛回退
当无法运行 parse_check.mjs(无 JS runtime)时,按 references/syntax-scan-and-fix-rules.md 的「静态语法初筛」节用 grep 定位可疑行,再按高风险模式表处理,并在报告中说明未做 parse 验证的原因。例如:
python3 - <<'PY'
from pathlib import Path
import re
patterns = [
r'^\s*\["',
r'--?>\s*\["',
r'\.->\s*\["',
r'^\s*subgraph\s+"[^"]+"\s+\[',
r'\s--\s*"',
r'\s--"',
]
compiled = [re.compile(pattern) for pattern in patterns]
for path in sorted(Path("docs").rglob("*.md")):
for line_number, line in enumerate(path.read_text(encoding="utf-8", errors="ignore").splitlines(), 1):
if any(pattern.search(line) for pattern in compiled):
print(f"{path}:{line_number}:{line}")
PY
三类修复规则
Flowchart 边 Label:使用 pipe label 语法。当 label 含有 (、)、/、:、<、>、, 或其他标点时,优先加引号:
A -->|"label with (parentheses), /slashes, or :colon"| B
Flowchart 节点 Label:节点形状内的 label 如果包含标点,应加引号:
B{"get_scene_file(scene_name, platform)"}
C["/base_velocity_controller/commands (std_msgs/Float64MultiArray)"]
State Diagram Transition Label:stateDiagram-v2 使用 A --> B: label。如果 label 自身还需要额外冒号,应把内部冒号转成 Mermaid entity 文本:
Idle --> GoalEvaluation: handle_goal()<br/>episode_recorder.py#58;277-288
这里的 #58; 是冒号的 HTML entity 写法,渲染后显示为 file.py:277-288,从而保留原内容——这是「除 Mermaid 语法转义外,保持可见 label 不变」原则的典型体现。
六、构建验证:真实文档构建与产物扫描
修复后必须运行真实文档构建。项目有文档化命令时优先使用项目命令,常见 Sphinx 命令包括:
sphinx-build -M html source build
或从文档项目根目录运行:
python3 -m sphinx -b html docs/source docs/build/html
随后用技能脚本扫描生成 HTML(bash 与 Windows PowerShell 均可直接运行;匹配文件逐行输出到 stdout,扫描统计输出到 stderr):
python -X utf8 <skill_dir>/scripts/html_scan.py --build-dir docs/build/html --scan errors
python -X utf8 <skill_dir>/scripts/html_scan.py --build-dir docs/build/html --scan cdn
--scan errors:扫描 Mermaid 错误文本(Syntax error in text/Parse error/mermaid version/Diagram error)。期望退出码 0 且 stdout 无匹配文件;出现任何匹配都是失败。--scan cdn:扫描是否意外引入在线 runtime 依赖(cdn.jsdelivr.net/unpkg.com)。对要求离线可用的文档,期望退出码 0 且 stdout 无匹配文件;允许在线的文档出现匹配时,须在报告中说明在线依赖及处置。- 构建目录缺失或其中没有 HTML 文件时,脚本以退出码 2 报错——此时不得视为验证通过,应先修正
--build-dir或重新构建(scripts/html_scan.py 明确定义了退出码语义)。
再用同一脚本确认本地 runtime 文件已复制到生成产物的 _static 目录(stdout 逐项列出找到的资产,可直接作为报告「已检查的 Mermaid runtime 文件」字段的证据):
python -X utf8 <skill_dir>/scripts/html_scan.py --build-dir docs/build/html --scan runtime
- 使用本地浏览器 runtime 的项目(UMD:
mermaid.min.js+ runner;ESM:.mjs入口 +chunks/)期望退出码 0。 - ESM 入口存在但旁边没有
chunks/目录时,脚本输出 WARNING 并以退出码 1 提示核查 ESM runtime 是否完整。 - 服务端渲染(png/svg 直出)的项目没有浏览器 runtime 属正常,退出码 1 不代表失败,但须在报告中说明渲染方式。
七、浏览器验证:最终渲染防线
Sphinx 成功并不够——Mermaid 语法通常是在浏览器端才真正解析。因此修复后必须从生成的 HTML 根目录启动临时本地服务:
python3 -m http.server 8765 --bind 127.0.0.1 --directory docs/build/html &
SERVER_PID=$!
使用浏览器自动化打开每个包含 .mermaid 的 HTML 页面,并检查每个 Mermaid 容器:
- 包含
svg; - SVG 的
aria-roledescription不是error; - 文本不匹配
Syntax error in text|Parse error|Diagram error|mermaid version。
如果项目实现了点击放大功能,还要验证交互:
- 点击渲染后的
.mermaid图会打开.mermaid-modal或项目自定义 modal; - 滚轮输入会改变克隆 SVG 的 transform 或缩放状态;
- 支持拖拽时,拖拽会移动放大后的 SVG;
Escape、关闭按钮、点击背景均可关闭 modal。
验证后必须关闭临时服务:
kill "$SERVER_PID"
八、最终报告格式
技能要求最终报告必须包含以下字段,缺失即视为未执行对应门禁步骤:
context_check.py的输出摘要(文档栈、runtime 文件、fence 数、含图页面数);- 修改的文件;
- 修复的具体语法类别(存在手工修复时须注明依据:高风险模式表的行或「修复规则」章节的条目,作为审计记录);
parse_check.mjs结果(N/N 块通过);- 已检查的 Mermaid runtime 文件,包括本地/CDN 结果;
- docs 构建结果;
- 浏览器验证统计:检查页数、Mermaid 图数量、失败数;
- 如果项目实现点击放大,包含点击放大验证结果;
- docs 构建中剩余的非 Mermaid warning;
- 确认临时服务已关闭。
技能内置的示例报告:
Context check: myst_parser + sphinxcontrib.mermaid, 211 fences, local runtime mermaid.min.js + mermaid-run.js, 56 HTML pages with mermaid, no CDN.
Parse check: 211/211 mermaid blocks passed (3 manual fixes per pattern table rows: subgraph legacy syntax, stateDiagram colon).
Changed 4 Markdown files, syntax-only Mermaid edits.
Runtime check: local mermaid.min.js + mermaid-run.js copied, no CDN runtime references.
Sphinx build: passed.
Browser Mermaid QA: 56 pages, 142 diagrams, 0 failures.
Click-to-zoom QA: modal open, wheel zoom, drag pan, ESC close passed.
Generated HTML error text scan: no output.
Temporary HTTP server stopped.
九、禁止事项与红线清单
- 不要只依赖静态 grep 或
sphinx-build; - 不要为了避免语法错误而删除 label;
- 除非用户明确要求,不要改变图语义、来源引用、标题或正文;
- 除非用户明确要求,不要修改
sphinxcontrib-mermaid等依赖版本号; - 对要求离线可用的文档,不要重新引入 CDN runtime 依赖;
- 不要让临时 HTTP 服务残留运行。
十、在仓库中的落地:脚本实现与真实文档佐证
脚本实现要点
四个脚本构成了完整的「检查 → 诊断 → 修复 → 复验 → 产物扫描」工具链,可以从源码层面印证本文描述的流程:
- scripts/context_check.py:通过
CONF_KEYS列表(extensions、myst_fence_as_directive、html_js_files、mermaid_version、mermaid_output_format、mermaid_init_config、mermaid_include_elk、mermaid_fullscreen、mermaid_use_local等)逐项正则提取conf.py配置;RUNTIME_FILES定义 runtime 资产清单;CDN_RE匹配cdn.jsdelivr.net|unpkg.com。若发现conf.py中既无sphinxcontrib.mermaid也无myst_parser,或存在 mermaid 容器但无本地 runtime script 引用,会输出 WARNING。缺失 conf.py / 构建目录时仅报告而不视为错误(仓库可能对接外部 Sphinx 项目)。 - scripts/parse_check.mjs:以
jsdom+mermaid.initialize({ startOnLoad: false, suppressErrorRendering: true })建立与浏览器一致的解析环境,逐个 fenced block 调用mermaid.parse(),输出total/passed/failed统计并打印失败块的文件、块号、错误信息与块内容头部。 - scripts/fix_labels.py:
BLOCK_RE只触碰```mermaidfenced block;DIAGRAM_HEAD_RE限定仅处理 graph/flowchart 图;STATMENT_KEYWORDS(classDef、style、click、%%、subgraph、end等)保护的语句行跳过;支持--dry-run预览;处理 UTF-8 BOM 文件时保持 BOM 不变;由于逻辑幂等,对已修复的源再次运行会报告 0 处变更。 - scripts/html_scan.py:
ERROR_TEXT_RE匹配四种 Mermaid 错误文本;CDN_RE匹配在线 runtime 依赖;runtime扫描把_static/(含_static/js/)下找到的 runtime 资产逐项输出到 stdout,作为报告的运行证据;ESM 入口无chunks/时给出 WARNING 并返回 1。
仓库中的真实 Mermaid 图样例
本仓库的 docs/ 目录本身就是该技能的适用对象。例如 docs/unify.md 的总体架构图使用 flowchart TB,并且大量采用了技能文档要求的「稳定节点 id + 带引号标题」规范写法,如:
flowchart TB
subgraph ENTRY["ROS 进程与业务入口"]
DISPATCHER["ScheduledActionDispatcherNode<br/>产品动作调用方"]
SCHEDULER["GlobalInferenceSchedulerNode"]
end
而 docs/roadmap.md 的里程碑路线图使用 graph TD 与 subgraph "阶段 N: ..." 的中文子图命名。这两类图正好覆盖了技能「subgraph 写法兼容」「中文/标点标签转义」等典型检查点;docs/unify.md 的「策略 Facade」流程图还展示了 STAGES -->|"Torch / single-role"| SESSION 这类 pipe label 加引号的推荐写法。
在文档迁移流水线中的位置
mermaid-syntax-validation 是文档迁移流水线的最后一道质量闸门。按 docs/migration/README.md 的流程,翻译和生成完成后即使用该技能对 Mermaid 图进行语法检查、最小修复和渲染验证,推荐检查内容包括:统计 Mermaid fence 数量、静态扫描高风险语法(匿名节点、旧式边标签、带括号或冒号的未加引号标签)、修复后执行真实 Sphinx 构建、浏览器确认渲染正常且无错误文本;当 DeepWiki 源内容更新时,增量更新流程的最后一步同样是运行 mermaid-syntax-validation(docs/migration/README.md)。
整体而言,该技能把「Mermaid 图在文档发布链路中渲染失败」这一高频问题,收敛为可脚本化、可审计、可复验的四阶段门禁流程,其「只修语法、保留语义」的边界与「报告字段必须取自脚本输出」的证据纪律,保证了任何人工干预都留有依据、任何验证结论都可追溯——这也是它在 .agents/skills/README.md 中被列为独立文档技能的原因。