AI输出满屏井号星号?hmharness把Markdown渲染成终端表格 GitHub:swsgbl/hmharness

0 阅读6分钟

先说工具全貌:hmharness 是一个开源的 HarmonyOS/OpenHarmony 开发智能体框架,把工程创建、检查、构建、签名、安装、启动、日志读取和结果验证接成本地工具链,让 AI 不只是生成代码,还要用本机环境证明结果;自进化行为受审批、预算、canary 和回滚约束。它不是 DevEco Studio 的替代品,而是开发智能体和鸿蒙工具链之间的验证层。本文只展开其中一环:TUI Markdown 渲染器。

仓库:github.com/swsgbl/hmha…

用过终端 AI 工具的人大概率见过这种输出:标题前挂着 #,重点两边夹着 **,表格是一排竖线和横线,中文一进来竖线就开始漂移。模型没有写错,它输出的是标准 Markdown;真正的问题是终端把 Markdown 源码原样透传给了人。

hmharness 的做法是把渲染责任收回来:模型继续输出标准 Markdown,CLI 在终端里把它渲染成可读版式。标题分层、粗体和行内代码有终端样式,表格变成带边框的表格,中文按双宽字符对齐,流式输出时未闭合块可以重排,已闭合块保持稳定。

GitHub 仓库入口

1. 原样透传为什么难读

Markdown 对模型很友好:# 表达层级,** 表达重点,| 和 - 表达表格结构。可是这些符号的价值依赖渲染器。网页和编辑器会解析它们,终端默认只把每个字符当文本画出来。

于是同一个回答在网页里是“结构化内容”,在终端里变成“带语法噪音的文本”。表格尤其明显:列宽依赖字符宽度,中文、英文和 emoji 的显示宽度不同,简单用字符数量补空格,竖线一定会错位。

Markdown 原样透传的问题

hmharness 引入的是 packages/cli/src/markdown.ts:一个零依赖的终端 Markdown 渲染器。这里说的“零依赖”不是营销词,而是实现边界:解析、宽度计算、换行、表格和 SGR 样式都在这个模块内完成,不为渲染再引入外部包。

2. 标题、粗体、行内代码和链接怎么变

渲染器先分块,再处理行内格式。块级规则负责标题、表格、列表、任务项、引用、代码块和段落;行内规则处理粗体、斜体、行内代码、链接、转义和 HTML 标签。

对应到终端里的效果是:

  1. # / ## 不再露出井号,而是变成有层级感的标题样式,一二级标题附带横线;
  2. **重点** 变成终端粗体,斜体在许多终端显示效果差,也按粗体处理;
  3. 反引号里的代码用反显样式,让命令和标识符一眼能区分;
  4. [文本](链接) 显示为带下划线的文本,避免长 URL 把版面撑爆;
  5. 未闭合的 ** 保持原样,流式输出到一半时不提前吞掉用户还没写完的符号。

行内与块级格式的转换

这套规则看起来细,但它是终端阅读体验的基础:用户要的是“这段话哪里是标题、哪里是命令、哪里是重点”,不是重新学习 Markdown 语法。

3. 表格的关键不是画线,而是宽度

表格是 Markdown 渲染里最容易翻车的部分。hmharness 的处理分三步:

  1. 识别表头、对齐标记和数据行;
  2. 按显示宽度计算列宽,中文等 CJK 字符按双宽计入;
  3. 如果整个表格超过终端宽度,优先压缩最宽的列,压缩到边界后用省略号截断。

CJK 表格对齐与超宽列压缩

这样处理后的表格有真实边框、有表头加粗、有左右和居中对齐。更重要的是,中英文混排时竖线能对齐,因为计算的是显示宽度,不是 JavaScript 字符串长度。

这不是“尽力美化”,而是有测试锁定的行为:测试里专门覆盖 CJK 双宽对齐、表格边框、列对齐、转义竖线、超宽列压缩和省略号截断。

4. 流式输出:未闭合块重排,已闭合块不变

AI 回答不是一次生成的。流式场景下,代码块、表格和段落都可能处在“还没写完”的状态。如果每来一个 chunk 就把所有历史内容重排一次,屏幕会持续跳动,用户已经读过的内容也会变位置。

hmharness 的策略是把稳定性作为渲染合同:

  1. 已闭合块渲染后不再变化;
  2. 未闭合的段落、代码块或表格允许随新内容重排;
  3. 相同输入和相同宽度必须得到相同输出;
  4. 未闭合的格式标记保持字面量,等闭合后再应用样式。

流式输出与确定性边界

这让 TUI 可以边生成边刷新,同时保证已读内容不乱跳。对开发智能体来说,这个细节直接影响信任:用户需要能在模型继续输出时稳定阅读前面的证据和结论。

5. 怎么复现证据

本文事实钉在当前 public main 提交 c6fdd73422ef8cb05d41412ed448e7d4e8f5a226。功能提交是 67b220ee99016ccdf0b561467f8fc84330fe4f5a,合并提交 46e887d,随 0.23.44 引入。

只读复现命令:

git clone https://github.com/swsgbl/hmharness.git
cd hmharness
git checkout c6fdd73422ef8cb05d41412ed448e7d4e8f5a226
npm install
node --import tsx --test packages/cli/src/__tests__/markdown.test.ts

当前聚焦 Markdown 测试为 28 pass / 0 fail,覆盖标题、代码块、引用、表格、列表、任务项、行内格式、CJK 宽度、换行、超宽截断和流式确定性。

同版本 CI run 41398333044 已通过,汇总为 1068 tests / 1067 pass / 0 fail / 1 skipped。

安装与发布边界分开说:npm 当前在线包是 @hmharness/cli@0.23.50,registry 读回的 gitHead 是 c6fdd734...;GitHub Latest Release 仍是 v0.23.26。这两个数字代表不同发布面,不能混写成“最新版全部已发布”。

6. 为什么这件事值得单独做

hmharness 的核心不是让模型说得更漂亮,而是让开发者在终端里更快读懂证据。Markdown 渲染器看起来只是显示层,但它决定了命令、表格、风险说明和验证结果能不能被低摩擦消费。

如果一个智能体框架要求用户先在脑内解析 Markdown,再手动判断表格列对应关系,它就在给验证链路增加噪音。反过来,终端输出越接近模型的语义结构,用户越容易发现“哪里是命令、哪里是风险、哪里已经被本机验证”。

如果你在不同终端、不同 OS 或不同 Node 环境下仍看到符号透传、表格错位或流式跳动,欢迎把环境信息反馈到 GitHub Discussions #4,尤其是 OS 版本、终端名称和版本、Node 版本。

参考: