☰
Explainshell 渲染评估指南:用 eval-render 系统化评测 mandoc Markdown 渲染输出
2026/10/7 14:19:25 网站建设 项目流程
  • 后端
  • 开发工具

【免费下载链接】explainshell

match command-line arguments to their help text

项目地址:https://gitcode.com/gh_mirrors/ex/explainshell
点击查看免费下载

本指南完整讲解 Explainshell 仓库中eval-render技能(.claude/skills/eval-render/SKILL.md)的用法:在升级tools/mandoc-md前,如何对候选 mandoc 二进制做端到端渲染评估、解读结构指标差异、判定合并/回退/延期结论,并在出现回退时生成可交付给 mandoc 源码工作区 Agent 的交接提示。读完本文,你将能独立跑通"渲染基线→渲染候选→对比→分类→截图报告→裁决→交接"的完整评估闭环,并理解其背后的源码机制。

评估工作流总览

eval-render的本质是回归评审工具而非黄金快照测试(golden snapshot test)——这一点在其底层实现 render_eval.py 的模块 docstring 中有明确说明。评估不追求"输出与预期逐字节一致",而是回答三个问题:

  1. 一次 markdown 渲染改动,是否影响了原本渲染正常的 manpage?
  2. 渲染出的 HTML 结构是否出现意外变化?
  3. 已知的压缩选项清单(如 ImageMagick 页面)是否在结构上变得更有用?

工作流共七步:渲染基线与候选、对比、逐页分类、生成截图 diff、套用裁决标准、生成交接提示(仅回退时)、输出最终报告。候选二进制通常是打了补丁的 mandoc 源码树,例如~/dev/vibe/mandoc-1.14.6/mandoc,而基线是仓库当前捆绑的tools/mandoc-md。

调用语法(该技能可由用户直接触发,即user_invocable: true):

/eval-render <candidate-mandoc> [--mandoc-worktree <path>]
参数必填说明
candidate-mandoc是补丁后 mandoc 二进制的绝对路径,如~/dev/vibe/mandoc-1.14.6/mandoc
mandoc-worktree否构建候选二进制的 mandoc 源码工作区路径,用于交接提示;省略时从候选二进制的父目录推断

第 1 步:渲染基线与候选

先后执行两次渲染,全语料(corpus)每次约耗时 10–30 秒:

source .venv/bin/activate && \ python tests/evals/render/render_eval.py render --label baseline-tools-mandoc-md --mandoc tools/mandoc-md python tests/evals/render/render_eval.py render --label candidate-<short-tag> --mandoc <candidate-mandoc>

选择一个能区分候选的短标签,例如paragraphize-fix、synopsis-v2。两次运行的输出末尾都会有run directory:行,记下两个运行目录的路径,供后续compare使用。

底层机制:render_run()(render_eval.py)逐页调用_mandoc_markdown()(render_eval.py),即对每个 manpage 执行mandoc -T markdown,非零退出码或空输出会抛出异常并被记为 failure;随后用 web/markdown.py 的render_markdown()渲染 HTML(内部先转义裸<word>占位符再走 cmark-gfm 的cmarkgfm.markdown_to_html,出错时回退为转义文本),并对 markdown、过滤后文本与 HTML 三份产物分别计算指标。每个运行目录下生成三类工件:

  • markdown/*.md:mandoc-T markdown原始输出
  • html/*.html:经 Explainshell cmark-gfm 路径渲染的 HTML
  • metrics/*.json:每页的结构指标

运行元数据(label、timestamp、mandoc 路径、git 提交、语料列表、页面数与失败数)写入summary.json。渲染结束会打印rendered pages:与failures:统计;--fail-on-failure可在有页面渲染失败时令进程以非零码退出,适合 CI 风格场景。未显式指定--mandoc时默认使用config.MANDOC_PATH(config.py 中的环境变量,缺省指向仓库内tools/mandoc-md)。

第 2 步:对比两次运行

source .venv/bin/activate && \ python tests/evals/render/render_eval.py compare <baseline-run> <candidate-run>

完整阅读输出。其中"Suspicious structural changes"(可疑结构变化)一节列出了每一个有指标变动的页面;之后的"Metric deltas"一节给出每页具体指标的before -> after明细,comparison.md也会写入当前运行目录。

底层机制:compare_runs()(render_eval.py)通过_suspicious_changes()(render_eval.py)做逐页对比。该函数维护一张"指标→容差"检查表:结构性指标(如markdown.max_line_length、markdown.giant_lines_500、markdown.unescaped_star_runs、html.tags.h2、html.tags.tr等)容差全部为 0.0——只要出现非零 delta 就标为可疑;而markdown.char_count、html.data_chars这类内容量指标采用 0.02 的相对容差(abs(delta)/max(abs(before),1) > 0.02才算变化)。设计意图在注释中写得很清楚:"cheap structural metrics rarely move by accident",即便宜的结构指标很少意外变动,误报可容忍,而小的结构性改进恰恰是评估想要的信号。filtered.removed_sections的变化也单独列出。

第 3 步:为每个可疑页面分类

对每个可疑页面,在improvement(改进)、regression(回退)、ambiguous(不确定)三者中择一:

  • compare已经告诉了你哪些指标动了、动了多少——那就是检查本身。评估的标签集覆盖链接、代码、强调、标题、表格行、段落、列表与内容长度,任何语义上有意义的变化都会体现在 delta 中。读取每页的指标块,自问:"这些变动是否全部指向同一方向?该方向是否符合补丁声明的目的?",然后给出结论。
  • 当 delta 混杂、或以补丁描述无法解释的方式出人意料时,归类为ambiguous,不要猜测。

度量矩阵:每页指标分为三组(见_metrics(),render_eval.py):

  • markdown.*行级指标(_line_metrics(),render_eval.py):行数、非空行数、字符数、最大/平均行长、>500/1000 字符的巨型行数、类选项 token 数(正则(?<![\w\\])(?:\\?-{1,2}|\\\[mi\])[-A-Za-z0-9][-_A-Za-z0-9]*匹配)、单行多 token 计数、未转义的*与_连串数;
  • filtered.*过滤后指标(_filtered_metrics(),render_eval.py):先经 extraction/llm/text.py 的clean_mandoc_artifacts()清洗,再由filter_sections()(text.py)剔除无关章节,统计清洗后的行数/字符数与removed_sections;
  • html.*结构指标(_html_metrics(),render_eval.py):用标准库HTMLParser子类TagCounter统计a、blockquote、br、code、em、h1–h6、li、ol、p、pre、strong、table、tr、ul等 25 种标签的计数,以及文本字符数、最大嵌套深度和\ [ ] * _ \< >` 敏感字符直方图。

第 4 步:生成截图 diff 报告

后台运行截图对比,供用户(及必要时你自己)视觉核验。约 10 页 × 每页 ~10 秒:

source .venv/bin/activate && \ python tests/evals/render/render_eval.py diff <baseline-run> <candidate-run>

建议以run_in_background: true方式执行。diff 报告路径为<candidate-run>/diff-report/index.html。

底层机制:diff_report()(render_eval.py)为每个可疑页面生成 expected/actual 两个自包含 HTML 评审页(_write_review_page(),render_eval.py),调用npx playwright screenshot --browser chromium截图(_screenshot_page(),render_eval.py),超长页面自动回退到--clip-height 12000的视口裁剪模式(_screenshot_with_clip_fallback(),render_eval.py),再用_pad_screenshots_to_match()(render_eval.py)把较短图片顶部补白使两张 PNG 同尺寸,保证比较滑杆干净裁切。最终由_write_diff_index()(render_eval.py)生成单页索引:每页一张卡片,内含 slider / side-by-side / actual / expected 四种查看模式(基于img-comparison-slider)、触发标记原因、"Metric deltas" 折叠明细,以及指向 expected/actual 的 HTML 与 markdown 工件链接。页面下拉框支持 URL hash 直达与 hashchange 同步。若浏览器缺失,先执行npx playwright install chromium。

常用选项(对应 build_parser() 的 diff 子命令):

# 对全部页面截图,而不只是可疑页面 python tests/evals/render/render_eval.py diff BASE CURRENT --all # 迭代期限制报告规模 python tests/evals/render/render_eval.py diff BASE CURRENT --limit 3 # 把报告写到其他位置 python tests/evals/render/render_eval.py diff BASE CURRENT --output /tmp/render-diff # 自定义 Playwright 截图超时(毫秒,默认 30000) python tests/evals/render/render_eval.py diff BASE CURRENT --timeout 60000

第 5 步:套用裁决标准

  • merge(合并)⇢ 每个可疑页面都归类为 improvement。建议用户执行cp <candidate-mandoc> tools/mandoc-md并提交;同时建议跑一次/eval-llm作为后续的直觉检查(gut-check)。
  • regression(回退)⇢ 存在任一归类为 regression 的页面。进入第 6 步生成交接提示。
  • defer(延期)⇢ 存在任一 ambiguous 页面,或评审者自身不确定。询问用户如何继续。

注意cp与git commit只是建议用户执行的晋升命令,仓库本身是只读的,本文仅说明操作方式。

第 6 步:交接提示(仅回退时)

裁决为regression时,推断 mandoc 工作区路径(默认取候选二进制的父目录,除非显式给了--mandoc-worktree),然后产出一段自包含的提示,用户可将其粘贴到运行在该工作区的兄弟 Claude 会话中。提示必须包含:

  • 候选二进制的确切路径及其调用方式;
  • 语料运行目录(供上游 Agent 读取生成的 markdown/HTML);
  • 每个回退页面:页面路径、回退指标及 before→after 数值、根据指标模式推断的一行原因假设;
  • 下游渲染管线指引,便于上游 Agent 追踪其 markdown 会变成什么:Explainshell 把mandoc -T markdown输出经 web/markdown.py(CommonMark)渲染后才展示;
  • 成功标准:重建 mandoc,然后从 Explainshell 仓库重跑/eval-render <path>,确认裁决翻转为merge。

标准格式如下(生成时替换为实际值,回退页详情必须具体:真实文件路径、真实指标数字):

You're in the mandoc-1.14.6 source tree. Explainshell's render eval flagged the following regressions in your last build at <candidate-mandoc>: **Regressing pages**: - <page-path>: - <metric>: <before> -> <after> - Likely cause: <one-line hypothesis from the metric pattern> [repeat per page] **Rendering pipeline**: explainshell renders the `-T markdown` output through CommonMark (`explainshell/web/markdown.py`) before display, so anything CommonMark interprets specially (4-space indent → code block, blank lines → paragraph break, etc.) shapes the final HTML. Read the candidate markdown for the regressing pages to understand why the metric moved. **Reference artifacts** (read-only): - baseline markdown/HTML: <baseline-run>/{markdown,html}/ - candidate markdown/HTML: <candidate-run>/{markdown,html}/ **Iterate**: 1. Make a fix in the mandoc source. 2. `make` to rebuild. 3. From <explainshell-repo>, run `/eval-render <candidate-mandoc>`. 4. Stop when the verdict flips to "merge".

为什么强调 CommonMark:Explainshell 的展示链路并非"mandoc 输出即所得"。render_markdown()会先转义<...>占位符,再交给 cmark-gfm 处理,因此 CommonMark 的特殊语义(4 空格缩进 → 代码块、空行 → 段落分隔等)会直接塑造最终 HTML。交接提示中的这条指引,正是让上游 Agent 去读回退页面的候选 markdown,理解指标为何移动。

第 7 步:输出最终报告

最终面向用户的报告(在聊天中给出,不写文件):

  • 一行结论(merge/regression/defer)及置信度说明;
  • 语料统计:总页面数、按分类拆分的可疑页面数;
  • 运行目录路径与 diff 报告 URL;
  • 每个可疑页面一行小结(<page>: <classification> — <one-line reason>);
  • 若merge:给出晋升用的确切cp+git commit命令;
  • 若regression:给出交接提示的 fenced block;
  • 若defer:列出触发延期的页面/指标,以及能够化解它们的证据。

语料组织与配置要点

语料文件为 tests/evals/render/corpus.txt,每行一个仓库相对路径的 manpage,#注释与空行被忽略(解析逻辑见_read_corpus(),tests/evals/_common.py)。路径经explainshell-manpagesgit 子模块(挂载于manpages/)解析,首次使用先初始化:

git submodule update --init

默认语料混合了三类页面:

  • 应当已渲染良好的 staple 页(grep、sed、ssh、tar等);
  • 大型选项密集页(curl、find、ps、xz等);
  • 已知 markdown 当前会折叠选项清单的 ImageMagick 页。

增删页面直接编辑corpus.txt;若只想渲染临时子集而不动语料,可在render后直接传路径:

python tests/evals/render/render_eval.py render \ --label imagemagick-only \ --mandoc ~/dev/vibe/mandoc-1.14.6/mandoc \ manpages/arch/latest/1/convert.1.gz \ manpages/arch/latest/1/magick.1.gz

render子命令的完整参数(build_parser()):--label(必填,人类可读标签)、--mandoc(二进制路径,默认取配置)、--corpus(默认tests/evals/render/corpus.txt)、--output(运行输出目录)、--fail-on-failure。

查看与检查建议

  • 视觉评审优先看diff命令产出的diff-report/index.html——每个可疑页面都有滑杆前后的 expected/actual 截图,并带回到渲染 HTML 与原始 markdown 的链接;
  • 纯数字评审看compare产出的comparison.md——列可疑结构变化及其背后的指标 delta。当评估"内容是否仍自然流动"时,优先审渲染后的 HTML 而非原始 markdown;
  • 支持 CI 式使用:compare BASE CURRENT --fail-on-suspicious在检测到可疑结构变化时以非零码退出;
  • 该工具不是黄金快照测试,刻意不接入make tests-all。应在修改tools/mandoc-md、web/markdown.py 或clean_mandoc_artifacts/filter_sections辅助函数时手动运行。

补充:无基线的绝对缺陷审计

render_eval.py还提供audit子命令(render_eval.py),对一次渲染运行做与基线无关的绝对渲染缺陷扫描,用于捕获两类信号:已知会产出quad_star_run的页面仍保留在语料中(见 corpus.txt 注释),保证audit有信号可查。内置 11 条规则(AUDIT_RULES,render_eval.py):

rule_id扫描内容
quad_star_run4+ 星号连串(如**foo****bar**)
empty_emphasis_tag空<em>/<strong>标签
roff_named_escape\[name]形式泄漏到输出(对照 mandoc_char(7) 规范转义名集合KNOWN_MANDOC_ESCAPE_NAMES过滤误报)
roff_two_letter_escape\(xx形式泄漏到输出
roff_font_escape\fX字体转义泄漏到输出
visible_zwnj_entity文本中的字面&zwnj;实体
visible_nbsp_entity文本中的字面&nbsp;实体
visible_double_amp双重编码的&amp;amp;
visible_open_double_backtickmarkdown 中字面\``(不对称排版开引号)
giant_markdown_line超过 2000 字符的 markdown 行
synopsis_no_spaces_runSYNOPSIS 中 >200 字符且无空白符的行

已知渲染怪癖

.TP \(带\占位符的空标签段落)在 HTML 中渲染为<p>&nbsp;</p>——一个可见的空行块。早期版本的 mandoc-md 会将其输出为****行,CommonMark 会折叠成<hr />主题分隔线;当前二进制保留了作者"空段落"的原始意图。因此在每个此类位置,候选页面都比基线垂直更高,但不会出现伪水平分隔线。这是有意设计,评估时不应误判为回退。

  • 后端
  • 开发工具

【免费下载链接】explainshell

match command-line arguments to their help text

项目地址:https://gitcode.com/gh_mirrors/ex/explainshell
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询