中文EN

dsh-fs-encoding

tools已测通过npmMIT

文件编码守护:为 GBK、Big5、Shift-JIS、EUC-KR、Windows-125x、UTF-16、UTF-32 文件提供字节级无损读写与编辑,改完仍是原编码而不是被悄悄转成 UTF-8;UTF-8 BOM 原样保留,CRLF/LF/CR 换行符保持,编码表示不了的字符拒绝写入而不是写成「?」;非 UTF-8 文件会列出候选编码,模型可显式指定重读。

下载量 / Downloads
2,732
依赖体积 / node_modules
13.97 MB
测试时间 / Tested
2026-11-06
DSH 版本 / Version
0.2.2-rc.1

安装

dsh plugin --profile web add dsh-fs-encoding

沙箱测试结果

阶段结果
1 安装✓
2 列装✓
3 挂载✓
4 启动—

四阶段在隔离沙箱里跑:安装(有网)→ 列装 → 挂载 → 启动(后三步断网)。

README

dsh-fs-encoding — DSH 文件编码守护

为 DeepSeek Harness(DSH)提供的文件编码治理插件:让 AI 读写文件时不再弄坏 BOM 与多字节编码

🌏 中文 | [English](README_EN.md)

dsh · dsh-plugin · plugin · encoding · BOM · GBK · Big5 · Shift-JIS · UTF-16 · AI agent · 编码 · 文件编码 · 乱码

<!-- keywords: dsh, dsh-plugin, deepseek harness, plugin, encoding, bom, gbk, big5, shift-jis, utf-16, ai agent, 编码, 文件编码, 乱码 -->

简介

DSH 自带的 read / write / edit / grep 四个文件工具只认 UTF-8,遇到中文、日文、韩文项目里常见的编码就会出问题:

  • GBK、Big5、Shift-JIS 等编码的文件根本读不了,直接报 invalid UTF-8 text,AI 只能干看着;
  • 带 BOM 的 UTF-8 文件能读,但 BOM 会被悄悄吃掉——AI 随手改一次,文件开头那三个字节就没了。对 PHP、旧版编译器、部分 Windows 软件来说,文件头少了 BOM 就可能解析出错或直接乱码;
  • grep 搜非 UTF-8 文件时,命中的行显示成 (line is not valid UTF-8);搜中文时更糟——返回 0 条且不报错,看起来像"文件里没有",实际是根本没搜;
  • 更麻烦的是没有任何警告:你只会在某天发现文件坏了,却不知道是哪次编辑弄坏的。

本插件接管这四个工具,在完整保留原有行为(沙箱围栏、读后写保护、版本校验、diff 展示)的前提下,让非 UTF-8 文件和 BOM 都能正确读写:文件原来是什么编码,改完还是什么编码。

功能特性

  • 字节级保真:文件编码在首次读取时确定一次,之后每次保存都按原编码写回。改一个 GBK 文件,它仍然是一个 GBK 文件,不会被偷偷转成 UTF-8。
  • BOM 保真:原本有 BOM 就精确还原,原本没有就绝不擅自添加——两个方向都不会出错。
  • 换行符保真:CRLF / LF / CR 在读取时识别、保存时还原,Windows 项目不会被改成 LF。
  • 绝不静默损坏:如果目标编码表示不了新内容(比如往 GBK 文件里写 emoji),插件会直接拒绝写入并说明原因,文件保持原样——而不是写进去一堆 ? 把文件毁掉。
  • 读不了会告诉你怎么办:遇到非 UTF-8 文件时,插件会列出最可能的几种编码和各自的解码效果,AI(或你)选一个重新读即可,就像 VS Code 的「通过编码重新打开」。
  • grep 也能搜非 UTF-8 文件:命中的行显示真实内容而不是占位符;搜中文、日文时不会静默漏报。判定不出来的文件会附上候选编码,read 打开一次之后 grep 就跟着会用。
  • 十九种编码:UTF-8(含 BOM)、UTF-16、UTF-32,以及 GBK、Big5、Shift-JIS、EUC-KR 和 Windows-125x 全系(西欧、中欧、西里尔、希腊、土耳其、希伯来、阿拉伯、波罗的海)。
  • 零学习成本:read / write / edit / grep 的参数、返回格式与原版完全一致,是直接替换,原有提示词和工作习惯都不用改;read 和 write 各多了一个可选的 encoding 参数(write 的只用于新建文件,见下)。
  • 能建指定编码的新文件:write 加一个 encoding 就能直接生成 GBK、Shift-JIS 等编码的文件,不用先写 UTF-8 再转码。
  • 多三个工具:insert(按行号插入,原生没有这个能力)、undo_last_edit(撤销上一次编辑,内容与编码一并还原)和 str_replace_editor(4 个子命令的兼容层,同样带编码治理)。见下方「另外三个工具」。
  • 配置简单:一个 YAML 文件,几项开关,都能用环境变量覆盖。

使用

开箱即用

装好就能用,不需要任何配置。 插件没有必填项、没有初始化步骤,也不会改动你的项目——装上之后,AI 照常用 read / write / edit,编码的事插件自己处理:

  • 读 UTF-8 文件(含带 BOM 的):和原版完全一样,BOM 不会再被吃掉。
  • 读带 BOM 的 UTF-16 / UTF-32 文件:自动识别。
  • 改 GBK、Shift-JIS 等老编码文件:按原编码写回,不会被悄悄转成 UTF-8。
  • 写新文件:默认 UTF-8,需要别的编码时加一个参数即可。

这些都不需要你做任何事,也不需要 AI 改调用习惯。日常使用中,AI 的调用方式与原版完全一致。

AI 的调用区别

只有一种情况会不一样:无 BOM 的非 UTF-8 文件(典型是 GBK / Big5 / Shift-JIS 的老文件)。此时插件不会擅自猜,而是停下来让 AI 用显式编码重读一次:

[E_NOT_TEXT] legacy.txt is not valid UTF-8. Most likely gbk. Re-read with
read({ file_path: "legacy.txt", encoding: "gbk" }) to decode it, or set
autoGuessEncoding: true in the plugin config to decode automatically.
Candidates: gbk("你好,世界"), big5("斕疑"), shift_jis("ト羲")

AI 照着提示里的调用重读一次即可,它会自己完成——你不需要介入。重读之后编码就从「猜测」变成了「已知事实」,后续每次写入都按它进行。

read 和 write 因此各多了一个可选参数:

read({ file_path: "legacy.txt", encoding: "gbk" })
write({ file_path: "run.bat", content: "echo 中文\r\n", encoding: "gbk" })
无 BOM 的 UTF-16 / UTF-32 文件同理,用 read({ file_path: "<路径>", encoding: "utf16le" }) 显式指定即可正常读写(这类文件在 Windows 上较少见,且无 BOM 时无法可靠自动区分字节序,因此不做猜测)。
为什么默认要问一下? GBK、Big5、Shift-JIS 的字节范围在短文本上互相重叠,猜错在界面上是看不出来的——而且会按错误的编码写回,把文件彻底弄坏。所以插件默认选择「宁可失败,不可猜错」。如果你更希望它尽力解码,把 autoGuessEncoding 设为 true 即可。
即使开了 autoGuessEncoding,也有一种情况仍然会报错并列出候选:文件极短(几个字节),且两个独立的识别器给出了不同的答案。此时没有任何依据能判断谁对——实测这种情况下的首选有约 79% 是错的——插件宁可让你从候选里挑一个,也不替你赌一把。只要两者给出相同的答案,就会直接采用;文件稍长一些(几十字节以上)也基本不会再出现这种歧义。
另一种会报错的情况:识别器把文件判成了 ISO-8859-1 / Windows-1252 这类单字节编码,但按它解码出来的文本几乎全是非 ASCII 字符。真实的西文以字母和空格为主,不会长这样;而中文/日文/韩文/俄文的双字节被当成单字节读时,每个字符会变成两个拉丁字符,正好就是这个特征。这时插件不会采用这个判断,而是报错并列出候选——实测这一步能拦下 24 个本会被静默读错的文件,且不会误伤任何原本读对的文件。

新建指定编码的文件

默认新文件是 UTF-8(无 BOM)。要给旧系统生成一个 GBK 文件,加 encoding 即可:

write({ file_path: "run.bat", content: "echo 中文\r\n", encoding: "gbk" })

支持的编码名与 read 相同,别名和大小写都不敏感(cp936、Shift-JIS 都行)。名字里带 BOM 的(utf8bom、utf16le、utf16be、utf32le、utf32be)会写入对应的 BOM;其余都不写 BOM。编码表示不了的字符同样拒绝写入,不会写成 ?。

encoding 只对新建文件有效。 文件已存在时传它会直接报错 E_ENCODING_NOT_APPLICABLE,不会转换——因为「保持文件原有编码」是本插件的核心承诺,而转换会重写整个文件,AI 从返回结果里看不出区别。不要为了转换而先删除文件:删除会绕过「先读后写」闸门,让未读过的内容在返回结果里以 before: null 静默消失;而如果本会话读过该文件,删除后的第一次写入会报 FS_STALE_VERSION,重试一次即可重建。本插件不提供编码转换;确实需要一份另一种编码的副本时,请写到新路径上。

另外三个工具

除上面三个之外,插件还注册三个工具,同样带编码治理(写入按文件原有编码落盘)。

insert —— 按行号插入,原生没有这个能力。字面替换只能插在能引用到上下文的地方,「在文件开头加一行」之类就得先读到那一行再原样复述,很不方便:

insert({ file_path: "config.ini", insert_line: 0, new_string: "[core]" })

insert_line 指插入到该行之后:0 插到最前,等于文件行数时追加到末尾。行号与 read 显示的完全一致。

undo_last_edit —— 撤销某个文件的上一次编辑,内容与编码一并还原:

undo_last_edit({ file_path: "config.ini" })

改错了、或者 AI 发现自己上一次改动不对时用它。几条要点:

  • 只保留最近一次。同一个文件编辑两次,只有第二次能撤销;撤销一次后就没有了(不支持重做)。
  • 编码一起还原。如果那次编辑把 GBK 文件转成了 UTF-8(见 normalizeToUtf8),撤销会把文件变回 GBK,而不只是内容回去。
  • 文件被改过就拒绝。撤销前会核对文件是否仍等于那次编辑写入的内容;如果之后有人改过(你、其他工具、其他会话),撤销会拒绝并说明,绝不覆盖那些改动。
  • 只存在内存里。不写磁盘、不污染仓库,所以 DSH 重启后无法撤销(会明确告诉你没有可撤销的历史,而不是假装成功)。
  • 新建文件不产生撤销点:「撤销一次新建」等于删文件,破坏性太大,请直接删。

str_replace_editor —— 4 个子命令的兼容层,让按这套工具写的提示词与习惯直接可用:

命令 · 作用

view · 显示文件(带行号,可用 view_range 限定范围);路径是目录时列出两层结构,跳过隐藏项、node_modules、__pycache__

create · 新建文件,已存在则拒绝

str_replace · 替换唯一匹配;old_str 不唯一时拒绝并指出所在行号

insert · 同 insert 工具,insert_line 语义一致

undo_edit · 同 undo_last_edit,撤销上一次编辑

str_replace 额外接受一个可选的 replace_all:不传就是标准行为(要求唯一匹配),传 true 才全部替换。这是本插件加的唯一扩展参数。
原生的 str_replace 与 insert 只认 UTF-8,遇到 GBK 文件会直接失败或把文件转坏——这正是本插件接管它们的意义。

grep:搜索非 UTF-8 文件

grep 是对原生 grep 的直接替换:参数(pattern / path / include)、返回格式、搜索卡片都与原版一致。原生有两处问题,第二处更严重:

  • 匹配行显示不出来。非 UTF-8 文件里命中的行会被显示成 (line is not valid UTF-8),明明内容就在文件里。
  • 非 ASCII 模式静默漏报。原生把模式当字节序列匹配,UTF-8 的模式字节在 GBK 文件里不可能出现,所以搜中文时返回 0 条、不报错——看起来像"文件里没有",实际是"根本没搜"。

本插件的处理方式,按模式是否含非 ASCII 字符分两条路:

  • ASCII 模式(如 port):仍由 ripgrep 一次搜完,只对真的出现乱码行的文件逐个判定编码后重搜。UTF-8 项目里这次重搜为 0 个文件,开销可以忽略。
  • 非 ASCII 模式(如 配置项

…(truncated)

以上内容来自 npm 上的 README,我们只做格式化,不改内容。

版本

npm 上共发布:15,最后更新 2026-11-05

链接

GitHub · Homepage · npm

同类插件