npm package: @movemama/opencode-legacy
Description: OpenCode legacy text processing plugin for GB2312 and script-safe editing workflows.
Latest version: 2.0.6

# @movemama/opencode-legacy

OpenCode legacy 文本处理插件，面向 `GB2312` 文本、脚本型 `.txt` 编辑和 legacy 规则路由场景。

当前对外推荐方案是只安装插件，让客户通过 `opencode plugin` 或 `opencode.jsonc` 直接使用；不要求客户额外下载 launcher 脚本。

当前主链路已经改为纯 JS 编码实现，不再依赖系统外部 `iconv.exe`。

## v1.0.9 新增能力

- **grep.js 编码 bug 修复**：`grep` 现在对所有命中 legacy 规则的文件统一使用 `decodeLegacyBuffer` 解码后再搜索，覆盖 GBK、GB18030、以及任意 `encoding` 字段，不再仅限 gb2312；同时支持 auto-detect 记忆编码作为 fallback；glob 引擎同步升级为 picomatch。
- **原子写入**：所有 `write` / `edit` / `legacy_write` / `legacy_edit` 路径改为先写 `.tmp` 再 `rename`，任意中途失败不会损坏原文件。
- **自动备份**：每次写入前自动在同目录 `.legacy-backup/` 保存带时间戳的副本，任何写操作都留有可还原记录。
- **`legacy_undo` 工具**：新增还原工具，可列出目标文件的全部备份（`list: true`）并按序号一键还原（`backupIndex: 0` 为最新）。
- **写入前字符兼容性扫描（`scanUnencodableChars`）**：写入 legacy 编码文件前自动预检内容，若含有目标编码不支持的字符，写入被拒绝并报告行号、列号和字符，避免静默替换为 `?` 导致内容损坏。
- **`legacy_replace_all` 批量跨文件替换工具**：支持在多个文件中同时替换文本；采用事务性设计——所有文件预检通过后才统一写入，任意文件失败则不写入任何文件；支持 `dryRun` 试运行模式；自动处理各文件的编码路由。
- **edit 失败智能诊断增强**：`buildEditFailureMessage` 现在包含：(1) Jaccard token 相似度滑动窗口推荐"最相似上下文"及行号、相似度百分比；(2) 编码不匹配检测（U+FFFD 替换字符 / 连续高位字节），给出可操作的原因提示。
- **变更 diff 视图增强**：编辑类工具返回的 diff 现在使用 Markdown `diff` 代码块包裹；删除行按红色、增加行按绿色渲染，中间省略行使用中性 `@@ ... @@` 标记，避免误显示成删除行。

## v1.0.8 新增能力

- **autoDetect 配置字段已暴露到 `legacy-rules.json`**：新增 `autoDetectEncoding`、`rememberDetectedEncoding`、`autoDetectCandidates`、`autoDetectMinConfidence` 四个根级字段，每个字段均有内联 `_comment` 说明，不再需要猜测字段含义。
- **编码记忆持久化**：自动探测到的高置信度编码现在会持久化写入 `<worktree>/.opencode/detected-encodings.json`，重启 OpenCode 后编码记忆不再丢失。
- **多规则字段继承合并**：同一文件命中多条规则时，最高优先级规则字段优先，未定义的字段自动从低优先级规则继承补充，无需在高优先级规则中重复填写所有字段。
- **glob 引擎升级（picomatch）**：内置 glob 引擎替换为 `picomatch`，全面支持 `!` 否定模式（如 `!backup/**`）和 `[abc]` 字符类，排除子目录或文件名前缀等需求可以直接在 glob 中表达。
- **新建文件编码推断提示**：autoDetect 开启时，若目标文件不存在，`write` 不再静默降级为 UTF-8，而是输出明确提示，告知用户需要在 `legacy-rules.json` 中配置显式规则。
- **classic-tag 编辑后链接校验**：编辑 `classic-tag` 格式文件后，插件自动校验所有 `@跳转目标` 是否存在对应 `[@标签]`，存在悬空链接时在写入成功的结果中追加警告，不再静默放行。
- **general-code fallback 明确标注**：`applyFallbackEditChain` 对 `general-code` 文件新增专属返回分支，不再以 `no-match` 模糊提示，而是直接说明"无专属 fallback，建议改用精确锚点替换或配置显式规则"，报错信息同步透出 reason。

## 规则注入

插件现在会在每轮对话自动读取并注入以下规则文件：

1. 全局 `AGENTS.md`：根据当前环境变量 `USERPROFILE` 或 `HOME` 动态解析到 `~/.config/opencode/AGENTS.md`
2. 项目 `AGENTS.md`：根据当前打开项目的 `worktree` 动态解析到 `<worktree>/AGENTS.md`

注入后的硬规则会进入系统上下文，用于约束代理优先读取规则、完整读取目标文件、以及统一输出回执。

另外，插件已为以下编辑类工具增加前置守卫：

- `edit`
- `write`
- `script-edit`
- `legacy_edit`
- `legacy_write`

如果当前会话尚未完成 `AGENTS.md` 规则注入，上述工具会直接拒绝执行，以避免代理在未读取规则的情况下改文件。

## 安装

当前对外推荐方案是只安装插件。

前置条件：

- Node.js `>=20`
- 已执行 `npm install -g opencode-ai`

命令方式：

```powershell
opencode plugin @movemama/opencode-legacy
```

或者在 OpenCode 配置中加入：

```json
{
  "$schema": "https://opencode.ai/config.json",
  "plugin": [
    "@movemama/opencode-legacy@latest"
  ]
}
```

首次未安装的用户会在 OpenCode 启动时自动安装当前 `latest` 版本。刚安装后如果没有生效，重新启动一次 OpenCode 即可让 npm 插件完成加载。

## 使用

安装插件后，客户只需要正常启动 OpenCode：

```powershell
opencode
```

插件会覆盖内置的 `read`、`write`、`edit`，让命中规则的 legacy 文件进入统一的编码路由和编辑策略链路。

推荐对外使用方式：

- 优先让模型调用被插件覆盖的 `edit`
- 在项目里通过 `legacy-rules.json` 描述哪些文件走 legacy 编码和对应策略
- 通过 `legacy_status` 诊断规则命中、编码来源和建议策略

## 卸载

如果要卸载插件本身，请执行以下操作：

1. 从 `~/.config/opencode/opencode.jsonc` 的 `plugin` 数组中移除 `@movemama/opencode-legacy`
2. 可选：删除 `~/.config/opencode/node_modules/@movemama/opencode-legacy`
3. 重启 OpenCode

## 关于原生 diff 视图

当前插件本身已经能产出 `metadata.diff` / `metadata.filediff`，但如果要让 `legacy_edit` / `legacy_edit_label` 在 OpenCode TUI 中像内置 `edit` 一样显示原生红绿 diff，最终仍需要 OpenCode host 支持“GenericTool 检测到 `metadata.diff + filepath` 时按原生 diff 渲染”。

这部分不适合作为客户安装步骤。更合理的最终方案是把这段能力合进 OpenCode 上游，这样客户仍然只需要：

1. 安装 OpenCode
2. 在 `opencode.jsonc` 中加入插件

在上游 host 尚未合入前，README 不把补丁版 host 作为默认客户方案。

## 当前提供的工具

- `read`
- `write`
- `edit`
- `script-edit`
- `legacy_read`
- `legacy_write`
- `legacy_edit`
- `legacy_status`
- `legacy_undo`
- `legacy_replace_all`

其中同名工具 `read`、`write`、`edit` 会覆盖 OpenCode 内置工具，实现 legacy 路由。

## 规则文件

包内自带 `legacy-rules.json` 作为默认规则。

加载优先级：

1. `<worktree>/.opencode/legacy-rules.json`
2. `<worktree>/legacy-rules.json`
3. 包内 `legacy-rules.json`

规则字段现在除编码外，还支持：

- `profile`
- `editStrategy`
- `fallbackMode`
- `scriptMarkers`

这让插件可以按场景决定更合适的编辑策略，而不只是按编码读写。

同一文件命中多条规则时，最高优先级规则字段优先，低优先级规则的字段在未被覆盖时会自动继承，无需重复填写。

## 自定义扩展规则

如果用户希望让其他文件类型也走 `GB2312` / `GBK` 的 legacy 读写链路，当前可以通过项目级规则扩展实现。

推荐在当前项目放置：

- `<worktree>/.opencode/legacy-rules.json`

例如：

```json
{
  "rules": [
    {
      "glob": "**/*.{npc,msg,dialog}",
      "encoding": "gb2312",
      "strict": true,
      "tool": "txt-gb2312",
      "priority": 50,
      "profile": "txt-gb2312-safe",
      "editStrategy": "exact-first",
      "fallbackMode": "legacy-safe-replace",
      "scriptMarkers": []
    }
  ]
}
```

字段说明：

- `glob`：匹配要走 legacy 读写链路的文件范围
- `encoding`：指定文件使用的编码，例如 `gb2312`、`gbk`
- `strict`：是否使用严格模式；通常脚本类文本建议保持 `true`
- `tool`：建议使用的处理器名称，当前常见值为 `txt-gb2312` 或 `legacy-text`
- `priority`：规则优先级，数值越大越优先匹配
- `profile`：场景配置名，用于帮助策略层判断文件类型与处理方式
- `editStrategy`：主编辑策略，例如 `exact-first`、`widget-field`
- `fallbackMode`：主策略失败后的回退方式，例如 `legacy-safe-replace`、`widget-field-update`
- `scriptMarkers`：用于辅助识别脚本型文件或 DSL 结构的关键标记数组

根级可选字段（用于未命中规则时的自动探测）：

- `autoDetectEncoding`：是否开启未命中规则文件的编码自动探测，默认 `false`
- `rememberDetectedEncoding`：是否记忆探测结果并在后续 `read/write/edit` 复用，默认 `true`
- `autoDetectCandidates`：探测候选编码列表，默认 `['utf8','gb18030','gbk','gb2312']`
- `autoDetectMinConfidence`：探测最小置信度，默认 `0.35`

如果客户只是想让某类新文件按 `GB2312` 读写，最小配置通常只需要先关心：

- `glob`
- `encoding`
- `strict`
- `tool`
- `priority`

其余字段可以先参考现有示例再逐步细化。

如果希望不依赖文件后缀而在“未命中规则 + 疑似中文乱码”时自动进入 legacy 编码链路，可在规则文件根级增加：

```json
{
  "autoDetectEncoding": true,
  "rememberDetectedEncoding": true,
  "autoDetectCandidates": ["gb18030", "utf8"],
  "autoDetectMinConfidence": 0.35,
  "rules": [
    {
      "glob": "**/*.txt",
      "encoding": "gb2312",
      "strict": true,
      "tool": "txt-gb2312",
      "priority": 10
    }
  ]
}
```

说明：

- 自动探测仅在“未命中规则”时触发，已命中规则仍以规则编码为准。
- 自动探测命中后会在读取回执中显示 `编码来源：auto-detect`。
- 若启用记忆，后续同一路径写入会优先使用记忆编码（回执显示 `来源：memory`）。

当前规则加载优先级为：

1. `<worktree>/.opencode/legacy-rules.json`
2. `<worktree>/legacy-rules.json`
3. `~/.config/opencode/legacy-rules.json`
4. 包内 `legacy-rules.json`

这意味着用户现在既可以：

- 在 `~/.config/opencode/legacy-rules.json` 中定义全局通用扩展规则
- 又可以在单个项目里通过 `<worktree>/.opencode/legacy-rules.json` 或 `<worktree>/legacy-rules.json` 做更高优先级覆盖

## 自动探测 legacy 编码

当前版本除了按 `glob` 命中显式规则外，还支持通过配置开启“自动探测 legacy 编码”兜底。

这意味着某些文件即使没有命中 `.txt`、`.npc`、`.cfg` 之类的显式规则，只要内容看起来像中文文本且按 UTF-8 读取结果不合理，插件也可以在候选编码中自动寻找更可读的解码结果，并切换到 legacy 链路。

推荐配置示例：

```json
{
  "autoDetectEncoding": true,
  "rememberDetectedEncoding": true,
  "autoDetectCandidates": ["gb18030", "gbk", "gb2312", "utf8"],
  "autoDetectMinConfidence": 0.2,
  "rules": [
    {
      "glob": "**/*.txt",
      "encoding": "gb2312",
      "strict": true,
      "tool": "txt-gb2312",
      "priority": 10
    }
  ]
}
```

字段说明：

- `autoDetectEncoding`：是否开启自动探测 legacy 编码
- `rememberDetectedEncoding`：是否记住某个文件上次探测成功的编码，便于后续写回继续沿用
- `autoDetectCandidates`：参与探测的编码候选列表，顺序可自定义
- `autoDetectMinConfidence`：最低探测置信度，低于该值则不自动接管

当前自动探测的行为特点：

- 不是只根据文件扩展名判断
- 不是简单地“看到乱码就一律当 GBK”
- 而是会对候选编码解码后的文本做可读性评分，选择更合理的结果
- 会优先保护合法且可读的 UTF-8 文本，避免把正常 UTF-8 中文误判成 legacy 编码
- 会比较候选编码之间的领先差值；若优势不明显，则不会自动接管
- 会尽量过滤明显非文本/二进制内容，降低误判率
- 对 `general-code` 类型文件会启用更保守的保护策略
- 自动探测结果会区分 `high / medium / low` 置信度等级
- 只有 `high` 置信度结果才会写入编码记忆并影响后续 `write`
- 读取成功后会在回执中显示 `编码来源：auto-detect`
- 如果开启了 `rememberDetectedEncoding`，后续 `write` 会复用记忆编码写回
- `legacy_status` 也会显示自动探测是否开启、候选编码列表和记忆编码

关于 `general-code`（如 C++ / C# / 强语法代码）的当前策略：

- 自动探测可以用于读取和判断编码
- 但如果文件是通过 `auto-detect` 命中的 `general-code`，默认会进入 conservative mode
- 在这种模式下，不建议直接做宽泛 `edit` 写入
- 更推荐：
  - 添加显式规则后再改
  - 使用更精确的小范围锚点替换
  - 或后续接入语法感知层再做更复杂编辑

当前兼容性与执行力边界可以理解为：

- 未知扩展名但内容明显是脚本 / NPC / rich-ui-dsl：自动探测后执行力较强，可直接走 `edit`、`script-edit` 或 widget 回退链
- 未知扩展名但内容是普通中文文本：自动探测后可直接读写，适合文本型替换
- 未知扩展名但内容是 `general-code`：当前更偏保守兼容，不承诺像脚本文本那样自动探测编码后稳定写回；如果需要更强执行力，建议添加显式规则或后续接语法感知层

`legacy_status` 当前会额外输出：

- `文本类型`
- `记忆编码`
- `编码来源`
- `置信度等级`
- `是否允许写入`

这样可以更快判断当前文件是“可安全修改”还是“只建议保守处理”。

如果客户的目标只是“某类固定文件永久按某种编码处理”，仍然建议优先写显式规则；自动探测更适合作为未命中规则时的兜底方案。

## 自动策略层

当前 `edit` 已经接入自动策略层。

- 普通文本默认走 `exact-first`
- `classic-tag` 文本可根据 `profile` 和 `scriptMarkers` 判断为更偏 block / line-normalized 的策略
- `rich-ui-dsl` 文本会优先识别 `<Text|...>` / `<Button|...>` 组件，并在 exact 失败后尝试 widget 字段级更新
- 失败时会返回当前策略、格式族和 fallback 提示，便于继续排障

## 支持的格式族

- `classic-tag`
  - 例如 `[@main]`、`<领取/@领取>`、`<关闭/@exit>`
  - 当前已支持：菜单行宽松匹配、标签块替换、跳转目标校验
- `rich-ui-dsl`
  - 例如 `<Text|x=...|text=...>`、`<Button|...|link=@...>`
  - 当前已支持：多行 widget 定位、字段级更新、children 字段保留

当前这两类是第一批重点支持对象。

- `general-code`
  - 例如 C++ / C# / 强语法代码文本
  - 当前仅做安全识别与保守回退，不做激进结构改写

## 诊断工具

可以使用 `legacy_status` 查看：

- 插件版本
- 规则来源
- 命中规则
- 格式族
- profile
- 建议策略
- fallback 模式
- 是否被判定为脚本型文件
- 自动探测是否开启、候选编码、最低置信度、记忆编码

## 如何确认已加载成功

最简单的方法是读取一个命中规则的 `.txt` 文件。

如果插件已接管成功，返回内容中会出现：

```text
--- Legacy 读取回执 ---
```

这说明 `read` 工具已经由插件覆盖并进入 legacy 规则链路。

也可以直接调用 `legacy_status` 检查当前文件会命中哪条规则、建议使用哪种策略。

如果检测为 `general-code`，当前插件会优先保持保守，不会像 rich-ui / classic-tag 那样做特化结构修改；这部分为未来语法感知层预留空间。

如果检测为 `classic-tag`，当前插件已能在 exact 失败后继续尝试：

- 菜单行 token 级匹配
- 标签块局部替换
- 编辑完成后自动校验悬空跳转目标（存在悬空 `@link` 时，写入结果中追加警告）

如果检测为 `rich-ui-dsl`，当前插件已能在 exact 失败后继续尝试：

- widget 字段级更新
- 多行 widget 定位
- 保留 `children={...}` 容器字段

## 兼容说明

- `txt-gb2312-tool.mjs` 目前仅保留为兼容遗留文件，不再是主链路依赖
- 当前主运行时入口以 `.js/.mjs` 文件为准

## 发布

```bash
npm login
npm publish --access public
```

## 开发测试

```bash
npm test
```