中文乱码如何被拦在提交前:AI Text Encoding Guard 实战

本文更新于 2026-07-26,内容对应 AI Text Encoding Guard v1.0.0。

中文文件经过一次 AI 编辑、批量替换或跨平台脚本处理后,为什么会突然变成乱码?

问题通常不在“中文”本身,而在文件读写链路:一段 UTF-8 字节被当成 GBK、GB18030 或 ISO-8859-1 解读,再以错误编码写回磁盘。Claude Code、Cursor、Copilot、Codex 等工具都可能经过终端、脚本和编辑器完成文件写入;只要其中一环没有固定编码,就可能触发或放大损坏。

AI Text Encoding Guard 的目标,是在这类问题进入提交或发布流程前,把可疑文件找出来,并对少数可逆样本尝试保守恢复。

先分清:有些乱码能恢复,有些不能

典型的 mojibake 往往仍保留原始信息:

错误显示:鐢ㄦ埛鐧诲綍鎴愬姛
正确文本:用户登录成功

如果只是“用错误字符集解释了同一组字节”,就有机会通过反向编码恢复。

但下面这种情况不同:

数据库连接失�

是 Unicode 替换字符,通常意味着某些原始字节已经丢失。工具可以可靠地把它标记出来,却不能凭空找回缺失内容。遇到这类有损损坏,优先从 Git、编辑器历史或备份恢复。

它如何工作

扫描文件 → 启发式信号打分 → 标记可疑位置 → 尝试保守恢复 → 重新扫描

当前版本的核心流程是:

  1. 扫描项目目录,默认覆盖 20 种常见文本扩展名。
  2. 逐行检查七类启发式信号,并记录命中的位置和权重。
  3. 将有命中的文件列为可疑项,交给开发者检查。
  4. 使用 --fix-gbk 时,尝试 UTF-8 / GB18030 双向候选恢复。
  5. 只有候选结果明显改善时才写入,并先创建 .bak.mojibake 备份。

七类启发式信号

这些名称是项目内部为了便于理解而使用的分类,不是字符编码领域的标准术语。

项目内分类 常见来源 检测信号 权重
口字码 解码失败或数据丢失 Unicode 替换字符 +12
破标签 编辑过程破坏结构 损坏的 HTML 闭合标签 +10
古文码 GBK/GB18030 与 UTF-8 混用 已知高频乱码码点 +2
锟拷码 多次错误转码 锟斤拷 等模式 +8
烫屯码 未初始化内存或调试填充值 烫烫烫屯屯屯 重复 +6
问句码 不可表示字符被替换 中文后连续 ?? +8
符号码 UTF-8 被当作单字节编码读取 拉丁扩展字符密集出现 +2

“破标签”严格来说不是字符编码乱码,而是经常和乱码同时出现的结构损坏,因此也被纳入扫描。分数表示命中了多少可疑信号以及信号权重,并不是经过统计校准的“损坏概率”。

自动恢复为什么比较保守

--fix-gbk 不会看到可疑文本就直接替换,而是经过三道检查:

① 生成 UTF-8 / GB18030 双向恢复候选

② 候选结果的可疑分数必须不高于原分数的 1/3

③ 绝对改善值必须至少达到 8 分

全部满足 → 备份原文件并写入
任一失败 → 跳过,留给人工检查

这些门槛用于降低误修风险,但备份和评分都不能代替人工审查。自动恢复后仍应查看 diff,并再次运行扫描。

安装与直接运行

当前最稳妥的方式是从仓库安装:

git clone https://github.com/haodehaode378/text-encoding-guard.git
cd text-encoding-guard
pip install -e .

安装后可以使用 CLI:

# 扫描项目
check-mojibake --root ./src

# 尝试保守恢复,并创建备份
check-mojibake --root ./src --fix-gbk

# CI 中发现可疑文件时返回退出码 2
check-mojibake --root . --fail-on-find

不安装也可以从仓库直接运行:

python scripts/check_mojibake.py --root ./src
python -m check_mojibake --root ./src

常用参数:

参数 作用
--json 输出 JSON,方便脚本和 CI 消费
--fail-on-find 发现可疑文件时返回退出码 2
--fix-gbk 尝试对可逆样本进行恢复
--ext .sql 添加额外扫描扩展名,可重复使用
--verbose / -v 显示详细诊断
--quiet / -q 减少控制台输出

项目运行时只依赖 Python 标准库,要求 Python 3.10 或更高版本。

接入 GitHub Actions

Action 必须先 checkout 目标仓库。当前存在的版本标签是 v1.0.0

name: Encoding Guard

on: [push, pull_request]

jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: haodehaode378/text-encoding-guard@v1.0.0
with:
root: '.'

还可以通过 fix-gbkext 输入控制自动恢复及额外扩展名。对 CI 来说,更推荐先只检测并阻止合并,再由开发者确认修复内容。

接入 Claude Code

如果项目中已经包含 scripts/check_mojibake.py,可以在 .claude/settings.json 中加入 PostToolUse hook:

{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "python scripts/check_mojibake.py --root ."
}
]
}
]
}
}

如果使用的是已安装 CLI,则把命令换成 check-mojibake --root .。关键不是绑定某一个 Agent,而是让编码检查紧跟在文件写入之后。

使用边界

  • 合法的生僻字、繁体字、拉丁扩展字符或乱码测试夹具可能触发启发式规则。
  • 的文本通常已经有信息损失,应优先回滚,而不是自动猜测。
  • .bak.mojibake 是恢复保险,不代表修改结果一定正确。
  • 工具不会替代 Git diff、代码审查或真实页面预览。
  • 默认会跳过 .gitnode_modulesdistbuild、虚拟环境和常见缓存目录。

fileuchardet 等工具更侧重识别文件编码;AI Text Encoding Guard 则把内容级启发式扫描、保守恢复、CI 卡点和 Agent hook 组合在一条工作流里。

仓库地址:https://github.com/haodehaode378/text-encoding-guard

问题和建议可以直接提交到 GitHub Issues。