@fxri/toolkit 1.13.1 → 1.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/README.md +2 -2
- package/dist/cli.cjs +73 -70
- package/dist/cli.js +73 -70
- package/package.json +2 -1
- package/skills/README.md +16 -12
- package/skills/fxri-plan-to-task/SKILL.md +3 -2
- package/skills/fxri-release-changelog/SKILL.md +3 -2
- package/skills/fxri-session-recap/SKILL.md +3 -2
- package/skills/fxri-text-encoding/SKILL.md +55 -47
- package/skills/fxri-text-encoding/references/boundaries.md +40 -0
- package/skills/fxri-text-encoding/references/troubleshooting.md +46 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fxri/toolkit",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.14.0",
|
|
4
4
|
"description": "专为多人 + AI 跨项目协作打造:任务管理 + 多语言 CHANGELOG。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
],
|
|
51
51
|
"dependencies": {
|
|
52
52
|
"@changesets/cli": "^2.31.1",
|
|
53
|
+
"@inquirer/prompts": "^8.7.5",
|
|
53
54
|
"commander": "^14.0.3",
|
|
54
55
|
"exceljs": "^4.4.0"
|
|
55
56
|
},
|
package/skills/README.md
CHANGED
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
|
|
7
7
|
## 技能列表
|
|
8
8
|
|
|
9
|
-
| 技能 | 版本 | 用途 |
|
|
10
|
-
| --- | --- | --- |
|
|
11
|
-
| [fxri-plan-to-task](./fxri-plan-to-task/SKILL.md) | 1.9.
|
|
12
|
-
| [fxri-release-changelog](./fxri-release-changelog/SKILL.md) | 1.2.
|
|
13
|
-
| [fxri-session-recap](./fxri-session-recap/SKILL.md) | 1.6.
|
|
14
|
-
| [fxri-text-encoding](./fxri-text-encoding/SKILL.md) | 1.
|
|
9
|
+
| 技能 | 版本 | 用途 | 层级 |
|
|
10
|
+
| --- | --- | --- | --- |
|
|
11
|
+
| [fxri-plan-to-task](./fxri-plan-to-task/SKILL.md) | 1.9.5 | 方案落盘:任务分解与纯展示计划表 → 逐仓逐任务建档 → 校验 → 归档 → 任务级规范沉淀(能力终点);规范载体迁移与升级 | core |
|
|
12
|
+
| [fxri-release-changelog](./fxri-release-changelog/SKILL.md) | 1.2.2 | changesets 发版与多语言 CHANGELOG 维护 | optional |
|
|
13
|
+
| [fxri-session-recap](./fxri-session-recap/SKILL.md) | 1.6.3 | 会话收尾全量沉淀 + 规范沉淀 / 新会话三层恢复 / 历史任务时间批量修正 | core |
|
|
14
|
+
| [fxri-text-encoding](./fxri-text-encoding/SKILL.md) | 1.2.1 | 文本编码:写脚本 / 配置 / 数据交换文本与跨进程 / 跨机器传递前按「生产者 × 消费者」定字节形态(临时任务同样适用),含非 ASCII 丢失 / 变形、乱码反查与首行报错 | optional |
|
|
15
15
|
|
|
16
|
-
版本号取自各 SKILL.md 的 frontmatter `metadata.version`(`toolkit skills status` / `skills install`
|
|
16
|
+
版本号取自各 SKILL.md 的 frontmatter `metadata.version`(`toolkit skills status` / `skills install` 打印的真源版本),与正文首部版本声明双写一致;层级取自 frontmatter `metadata.tier`(`core` 必装不可取消,`optional` 安装时可选,缺省按 `optional` 处理)。
|
|
17
17
|
|
|
18
18
|
## 安装
|
|
19
19
|
|
|
@@ -24,16 +24,20 @@
|
|
|
24
24
|
```bash
|
|
25
25
|
pnpm add -g @fxri/toolkit # 1. 装 CLI(npm 用户 npm i -g @fxri/toolkit)
|
|
26
26
|
toolkit skills install # 2. 把包内技能装到各 agent 的全局技能目录
|
|
27
|
+
# 先按 tier 选技能:core 必装、optional 可选(TTY 下方向键多选)
|
|
28
|
+
# 再选目标:canonical + 已检测 agent(--agent / --all-agents / --no-save-agents)
|
|
29
|
+
# --skills <names> / --all / --no-optional / --yes 可跳过技能与目标交互
|
|
27
30
|
# --scope project 换项目面:真源落 <仓库根>/.agents/skills/、其余候选目录落入口薄壳
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
toolkit skills
|
|
31
|
+
# --from <path> 临时外部来源(副本);--save [name] 登记永久来源(默认软链)
|
|
32
|
+
toolkit skills status # 查现场状态(悬空 / 指向其他版本 / 副本漂移 / 缺失 / 同名冲突 / 未选择但已安装;历史落点残留单列提示;--scope project 查项目面)
|
|
33
|
+
# 前五类重跑 toolkit skills install 补齐,同名冲突需 toolkit skills install --force 覆盖,未选择产物加 --prune 清理
|
|
34
|
+
toolkit skills remove # 卸载本包装的产物(只清自己装的,不碰用户自装技能;--skills 只清指定技能;--scope project 清项目面)
|
|
31
35
|
```
|
|
32
36
|
|
|
33
37
|
- 真源唯一:技能取自已装 CLI 包内的 `skills/`;软链锚在 pnpm 稳定入口,升级 CLI 后自动指向新版(副本形式需重跑 `toolkit skills install`)
|
|
34
38
|
- 目标三层:主目标 `~/.agents/skills/`(多家 agent 共读:Cline、Codex、Dexto、Kimi Code CLI、Loaf、Pi、Sarvam Code、Warp、Zed、DeepSeek Harness)→ 内置表内**已安装**的各 agent 全局技能目录 → `--dir <path>` 兜底(可多次指定,给表外 agent 用;只在当次安装时写入,技能增删后不会自动同步,需带相同 `--dir` 重跑 `toolkit skills install`);共读 agent 的全局目录即主目标本身,写一次即全部覆盖,`toolkit skills status` 单列「经 canonical 共读的 agent」;升级前的 Codex / Pi 历史落点(`~/.codex/skills`、`~/.pi/agent/skills`)若仍有本包登记,`toolkit skills status` 会单列「历史落点残留」提示清理(只提示、不自动删除)
|
|
35
|
-
- 默认软链(锚在 pnpm 稳定入口,升级不失效);链接创建失败自动降级为副本并打印 ⚠️(如无权限建链);`--copy` 强制副本、`--dry-run` 预演、`--force`
|
|
36
|
-
- 产物记录在状态文件 `~/.agents/.toolkit-skills.json
|
|
39
|
+
- 默认软链(锚在 pnpm 稳定入口,升级不失效);链接创建失败自动降级为副本并打印 ⚠️(如无权限建链);`--copy` 强制副本、`--dry-run` 预演、`--force` 覆盖同名非本包产物(不改本包已登记副本的形态)、`--prune` 清理未选择且内容一致的旧产物
|
|
40
|
+
- 产物记录在状态文件 `~/.agents/.toolkit-skills.json`(`format` + `schemaVersion` + `sources` 结构,旧结构首次读取时导入并备份 `.bak`);卸载 CLI 前先跑 `toolkit skills remove`,避免留下悬空链接(清理后目标目录若已空会一并回收)
|
|
37
41
|
- **项目面(`--scope project`)**:`--scope` 缺省时按 CLI 安装位置自动判定(项目内装 CLI → 项目面)。真源落 `<仓库根>/.agents/skills/`(完整副本),其余**已存在**的候选目录(`.trae/skills/`、`.claude/skills/`、`agent/skills/`(eve)、`.augment/skills/`、`.codebuddy/skills/`、`.continue/skills/`、`.crush/skills/`、`.devin/skills/`、`.goose/skills/`、`.grok/skills/`、`.hermes/skills/`、`.iflow/skills/`、`.junie/skills/`、`.kiro/skills/`、`.openhands/skills/`、`.qwen/skills/`、`.roo/skills/`、`.windsurf/skills/`)各放每个技能一份入口薄壳指向真源;产物记录在 `<仓库根>/.toolkit/state.json`(与真源、薄壳一并入库)。`toolkit init` 会写入 `prepare` 刷新钩子,`pnpm install` 时自动补齐/刷新,队友 clone 即用;`--copy` 仅全局面生效,项目面恒为副本
|
|
38
42
|
|
|
39
43
|
### 方式二:上游安装器 `npx skills`(需锁定文件或覆盖表外 agent 时)
|
|
@@ -3,14 +3,15 @@ name: fxri-plan-to-task
|
|
|
3
3
|
description: 将已确认的实施方案分解为可独立交付的任务并给出计划表,再落盘为标准任务文件并跟踪至归档:建档评估(查 active/archive 判同主题)、按模板建档、状态机更新、四级时间源取证、自查、任务级规范沉淀与归档。方案确认后表达「开始落地/登记记录」的意图,或即将改仓库文件时使用——含任务分解、建档、任务登记、落盘、归档、任务校验、规范载体迁移与升级(conventions 单文件升目录、v1 / v2 升 v3)等说法及口语近义表达(如按这个做吧、记一下、建个任务跟踪)均触发。不用于方案未确认的对话、会话收尾整场沉淀(fxri-session-recap 模式一)、与任务记录无关的普通 TODO、issue 管理或日常提交信息撰写。
|
|
4
4
|
license: MIT
|
|
5
5
|
metadata:
|
|
6
|
-
version: "1.9.
|
|
6
|
+
version: "1.9.5"
|
|
7
|
+
tier: core
|
|
7
8
|
author: fxri
|
|
8
9
|
source: https://github.com/fxri-net/toolkit
|
|
9
10
|
---
|
|
10
11
|
|
|
11
12
|
# 方案落盘:任务建档与归档
|
|
12
13
|
|
|
13
|
-
> 本技能版本 1.9.
|
|
14
|
+
> 本技能版本 1.9.5(随 @fxri/toolkit 同批分发)。被问版本时即报此值(不读磁盘、不跑 CLI);与 `toolkit skills status` 的磁盘值不一致即说明会话上下文已过期,开新会话即可。
|
|
14
15
|
|
|
15
16
|
## 何时使用
|
|
16
17
|
|
|
@@ -3,14 +3,15 @@ name: fxri-release-changelog
|
|
|
3
3
|
description: 基于 changesets 的发版与多语言 CHANGELOG 维护流程:创建变更集、消费发版、把分组标题与条目转为项目语言风格、清理变更集、打标签发布;无 changesets 的项目提供同格式手工模式。用户表达发版或记录变更意图时使用——含创建变更集、changeset、发版、version、CHANGELOG 格式化等说法及口语近义表达(发一版、出个版本、记一下这次改动、生成更新日志)。⚠️ 注意区分:用户说「提交个版本 / 先提交一版 / commit」通常指 git 提交当前改动(走任务收尾后提交),**不是发版**。不用于日常 commit message 撰写、git 提交操作或与发版无关的文档修改。
|
|
4
4
|
license: MIT
|
|
5
5
|
metadata:
|
|
6
|
-
version: "1.2.
|
|
6
|
+
version: "1.2.2"
|
|
7
|
+
tier: optional
|
|
7
8
|
author: fxri
|
|
8
9
|
source: https://github.com/fxri-net/toolkit
|
|
9
10
|
---
|
|
10
11
|
|
|
11
12
|
# 发版与 CHANGELOG
|
|
12
13
|
|
|
13
|
-
> 本技能版本 1.2.
|
|
14
|
+
> 本技能版本 1.2.2(随 @fxri/toolkit 同批分发)。被问版本时即报此值(不读磁盘、不跑 CLI);与 `toolkit skills status` 的磁盘值不一致即说明会话上下文已过期,开新会话即可。
|
|
14
15
|
|
|
15
16
|
## 何时使用
|
|
16
17
|
|
|
@@ -3,14 +3,15 @@ name: fxri-session-recap
|
|
|
3
3
|
description: 会话收尾的工作记忆沉淀 + 新会话开场恢复 + 历史任务时间修正:收尾时把整场会话的全部任务完整建档归档,按四级时间源还原真实完成时间,沉淀规范进 conventions 载体;新会话开场按三层恢复(全量索引 + active 精读 + 近窗归档)重建现场并核对规范;也可批量修正历史归档时间。用户表达会话收尾意图(今天先到这、收个尾、归档本次会话、把结论记下来)、接续意图(恢复上下文、继续上次、上次做到哪)或修正历史任务时间时使用。不用于会话中途的常规方案建档(那是 fxri-plan-to-task 的职责)、与工作交接无关的代码技术总结。
|
|
4
4
|
license: MIT
|
|
5
5
|
metadata:
|
|
6
|
-
version: "1.6.
|
|
6
|
+
version: "1.6.3"
|
|
7
|
+
tier: core
|
|
7
8
|
author: fxri
|
|
8
9
|
source: https://github.com/fxri-net/toolkit
|
|
9
10
|
---
|
|
10
11
|
|
|
11
12
|
# 会话归档、上下文恢复与历史修正
|
|
12
13
|
|
|
13
|
-
> 本技能版本 1.6.
|
|
14
|
+
> 本技能版本 1.6.3(随 @fxri/toolkit 同批分发)。被问版本时即报此值(不读磁盘、不跑 CLI);与 `toolkit skills status` 的磁盘值不一致即说明会话上下文已过期,开新会话即可。
|
|
14
15
|
|
|
15
16
|
## 何时使用
|
|
16
17
|
|
|
@@ -1,30 +1,47 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: fxri-text-encoding
|
|
3
|
-
description:
|
|
3
|
+
description: 在写脚本、配置、数据交换类文本文件之前,或用 shell 重定向、程序生成 / 导出、转码产出文本文件之前,或把非 ASCII 文本传给命令行参数、环境变量、API 请求体、模板渲染与远端元数据之前——不论临时、一次性、不入库、仓库内外——先加载本技能。判据只看读取方与字节形态:其默认解码不是 UTF-8(`powershell.exe` / `cmd` / Excel / MSVC / 未指定 `-encoding` 的 javac 等按系统代码页,bash / CI 按 locale),或格式要求 / 禁止 BOM、对行尾敏感,或写入经生成 / 转码通道。不按语言 / 扩展名白名单判断,Java / PHP 等按各自编译器与运行时默认解码推导。非 ASCII 内容与原文不一致(丢失、替换、截断、变形、mojibake)时立即用;`?` / `�` 只是表现之一,合法非 ASCII 与 `\uXXXX` / 百分号编码等表示形式不算故障。AI 写入工具默认不写 BOM,落盘后自查首三字节并读回比对。不用于 Base64 / URL 编码、排序计数、文件名规范化、CSV 语法、路径分隔符、时区、提交信息内容风格、任务建档、代码风格。
|
|
4
4
|
license: MIT
|
|
5
5
|
metadata:
|
|
6
|
-
version: "1.
|
|
6
|
+
version: "1.2.1"
|
|
7
|
+
tier: optional
|
|
7
8
|
author: fxri
|
|
8
9
|
source: https://github.com/fxri-net/toolkit
|
|
9
10
|
---
|
|
10
11
|
|
|
11
12
|
# 文本编码与字节形态
|
|
12
13
|
|
|
13
|
-
> 本技能版本 1.
|
|
14
|
+
> 本技能版本 1.2.1(随 @fxri/toolkit 同批分发)。被问版本时即报此值(不读磁盘、不跑 CLI);与 `toolkit skills status` 的磁盘值不一致即说明会话上下文已过期,开新会话即可。
|
|
14
15
|
|
|
15
|
-
##
|
|
16
|
+
## 触发判据(按读取方默认解码与字节形态判断,不按语言 / 扩展名白名单)
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
满足任一即加载:
|
|
19
|
+
|
|
20
|
+
1. **读取方编码**:即将写入或传输非 ASCII 文本,且读取方默认解码不是 UTF-8——`powershell.exe` / `cmd` / Excel / MSVC / 未指定 `-encoding` 的 javac 按系统代码页,bash / CI 按 locale。
|
|
21
|
+
2. **字节形态敏感**:可执行脚本与 shebang 文件(BOM / CRLF 会直接执行失败)、要求 BOM 的格式(`.ps1` 等)、禁止 BOM 且本次写入可能引入或移除 BOM 的格式(`.bat`、JSON、PHP 源文件等)。
|
|
22
|
+
3. **生成通道**:写入由 `>` / `>>`、`Set-Content` / `Out-File` / `Export-Csv`、模板渲染 / 占位符替换、程序生成或导出、`iconv` 转码完成——不论扩展名、临时与否、仓库内外。
|
|
23
|
+
4. **跨边界通道**:非 ASCII 文本传给命令行参数、环境变量、HTTP / RPC 请求体(项目描述、release、issue / PR 正文)、git / 远端元数据。
|
|
24
|
+
5. **执行通道**:PS 管道喂原生命令、重定向落盘、`Get-Content` 读无 BOM 文件、`chcp` / `iconv`、locale 默认编码、运行脚本。
|
|
25
|
+
6. **症状**:非 ASCII 内容与原文不一致——字符缺失、被替换、截断、长度异常、变形或 mojibake。
|
|
26
|
+
|
|
27
|
+
不加载:写入方与读取方都按 UTF-8(或按字节原样)处理,本次写入既不新增 / 移除 BOM、也不改行尾,且不经过生成 / 转码 / 跨边界通道;纯 ASCII 命令;纯展示输出且无异常。非 ASCII 本身不是故障,合法的日文、韩文、emoji、重音字符与 `\uXXXX`、百分号编码、HTML 实体、Base64 均按正常内容处理。临时、一次性、不入库、不提交、仓库外 temp 目录**均不豁免**——读取方可能在别的机器、Excel、CI 或远端平台,错误不会回到本会话。
|
|
28
|
+
|
|
29
|
+
只命中执行 / 跨边界通道时,直接跳「跑起来乱码」或「跨边界」节,不读文件侧基线。写盘前先定字符集、BOM、行尾三件套;AI 写入工具落盘后立刻判首三字节并读回比对(见「写盘」);出错先走乱码三问,文件侧查 `references/troubleshooting.md`,运行期查「跑起来乱码」。
|
|
18
30
|
|
|
19
31
|
## 先分归属(乱码三问)
|
|
20
32
|
|
|
21
|
-
|
|
33
|
+
故障是「写入方字节」×「读取方默认解码」;看到异常先走三问,不按字符外观定性:
|
|
34
|
+
|
|
35
|
+
1. **是谁的字节?** 先与预期原文或文件声明编码比对:字符都在、只是排列错 → 解码链问题;字符缺失 / 被替换 / 截断 / 空串 → 生产者编码失败。再重定向到文件看首字节,区分落点字节坏与终端 / 管线显示坏。
|
|
36
|
+
2. **还能救吗?** 能用已知错误解码链反向还原的是 mojibake(`涓枃` = UTF-8 被当 GBK 读,`䏿–‡` = UTF-8 被当 Latin-1 读,`ÖÐÎÄ` = GBK 被当 Latin-1 读,`锟斤拷` = GBK / UTF-8 反复转);出现替代符、字符缺失、截断、空串的是有损编码,原字节已丢,**不可逆**。替代符不限于 `?`,也可能是 `�`、`\uFFFD`、`�`、空白或空串。
|
|
37
|
+
3. **错在第几跳?** 只修认出的那一跳。`\u4e2d\u6587`、`%E4%B8%AD`、`中`、Base64 是表示形式,目标端接受就不动;`?` 是合法字符,孤立出现不判乱码。
|
|
38
|
+
|
|
39
|
+
第 2、3 问的处置——按特征串选反向转码(先备份原件,转完核对本意):
|
|
22
40
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
3. **错在第几跳?** `涓枃` = UTF-8 被当 GBK 读;`䏿–‡` = UTF-8 被当 Latin-1 读;`ÖÐÎÄ` = GBK 被当 Latin-1 读。只修认出的那一跳,别整条链重来
|
|
41
|
+
- `涓枃` 类 `iconv -f GBK -t UTF-8`;`䏿–‡` 类 `-f ISO-8859-1`
|
|
42
|
+
- 无 iconv 时:`[Text.Encoding]::UTF8.GetString([Text.Encoding]::GetEncoding(936).GetBytes($s))`,Latin-1 把 936 换 28591
|
|
26
43
|
|
|
27
|
-
|
|
44
|
+
读取方无 BOM 时的默认解码:
|
|
28
45
|
|
|
29
46
|
| 读取方 | 默认解码 |
|
|
30
47
|
| --- | --- |
|
|
@@ -33,23 +50,22 @@ metadata:
|
|
|
33
50
|
| `pwsh`、Node、Python 3、Go、Rust | UTF-8 |
|
|
34
51
|
| bash / sh / 各语言 `open()` | 按 locale,`LANG=C` 时为 ASCII |
|
|
35
52
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
- `涓枃` 类 `iconv -f GBK -t UTF-8`;`䏿–‡` 类 `-f ISO-8859-1`
|
|
39
|
-
- 无 iconv 时:`[Text.Encoding]::UTF8.GetString([Text.Encoding]::GetEncoding(936).GetBytes($s))`,Latin-1 把 936 换 28591
|
|
40
|
-
|
|
41
|
-
## 基线决策(按文件类型)
|
|
53
|
+
## 基线决策(按读取方推导,示例非白名单)
|
|
42
54
|
|
|
43
55
|
| 文件类型 | 字符集 | BOM | 行尾 | 理由 |
|
|
44
56
|
| --- | --- | --- | --- | --- |
|
|
45
|
-
| `.ps1` / `.psm1` / `.psd1` | UTF-8 | 有 | 均可 | `powershell.exe` 无 BOM 即按 ANSI
|
|
46
|
-
| `.bat` / `.cmd` | ASCII
|
|
57
|
+
| `.ps1` / `.psm1` / `.psd1` | UTF-8 | 有 | 均可 | `powershell.exe` 无 BOM 即按 ANSI 解码,非 ASCII 必炸 |
|
|
58
|
+
| `.bat` / `.cmd` | ASCII 优先;必须非 ASCII 时用系统 ANSI | 无 | CRLF | BOM 会让首条命令报错 |
|
|
47
59
|
| `.sh` / `Makefile` / 带 shebang 的脚本 | UTF-8 | 无 | LF | BOM 会污染 shebang |
|
|
48
|
-
|
|
|
60
|
+
| 常规源码 / 文档(`.md` / `.ts` / `.js` / `.yml` / `.toml` 等) | UTF-8 | 无 | LF | 工具链默认;本机同工具链内无特殊约束 |
|
|
61
|
+
| JSON / XML | UTF-8 | 无 | LF | JSON 带 BOM 多解析失败;XML 改字节而非只改声明 |
|
|
49
62
|
| `.properties` | Latin-1 + `\uXXXX`,或 `load(Reader)` 显式 UTF-8 | 无 | LF | `load(InputStream)` 固定按 ISO-8859-1 读 |
|
|
50
|
-
| C / C++ / Java
|
|
63
|
+
| C / C++ / Java 等按平台编码编译的源文件 | UTF-8 | 有;或加 `/utf-8` / `-encoding UTF-8` | LF | MSVC 与未指定 `-encoding` 的 javac 按系统代码页读源文件 |
|
|
64
|
+
| PHP 源文件 | UTF-8 | 无 | LF | `<?php` 前的 BOM 会被输出,触发 `headers already sent` |
|
|
51
65
|
| 交给 Excel 的 `.csv` | UTF-8 | 有 | 遵循目标平台 | 无 BOM 时 Excel 按 ANSI 解码 |
|
|
52
66
|
|
|
67
|
+
表是常见读取方的示例,不是可触发范围的白名单:新语言 / 新格式按三件事推导——读取方默认解码、是否要求或禁止 BOM、行尾是否敏感。
|
|
68
|
+
|
|
53
69
|
即便只在 `pwsh` 下跑也统一带 BOM——脚本常在别人机器上跑;`.psm1` / `.psd1` 同理。
|
|
54
70
|
|
|
55
71
|
## 写盘:谁在悄悄改字节
|
|
@@ -57,49 +73,37 @@ metadata:
|
|
|
57
73
|
| 写法 | 实际行为 |
|
|
58
74
|
| --- | --- |
|
|
59
75
|
| AI 文件写入 / 编辑工具;编辑器「保存为 UTF-8」 | 不写 BOM,须写完补或写入时前置 `\uFEFF` |
|
|
60
|
-
| `powershell.exe` `Set-Content` / `Add-Content` | 省略 `-Encoding` 按系统 ANSI 写,UTF-8
|
|
61
|
-
| `powershell.exe` `Export-Csv` 省略 `-Encoding` | 写 ASCII
|
|
76
|
+
| `powershell.exe` `Set-Content` / `Add-Content` | 省略 `-Encoding` 按系统 ANSI 写,UTF-8 非 ASCII 落盘即坏;`-Encoding UTF8` 写 BOM |
|
|
77
|
+
| `powershell.exe` `Export-Csv` 省略 `-Encoding` | 写 ASCII,非 ASCII 直接变 `?`;加 `-Append` 时编码又与新建不同 |
|
|
62
78
|
| `pwsh` `Set-Content` / `Out-File` / `>` | 默认 `utf8NoBOM`,要 BOM 显式 `utf8BOM` |
|
|
63
79
|
| .NET `[IO.File]::WriteAllText` / Node `fs.writeFileSync` | 均不写 BOM,须分别传 `[Text.UTF8Encoding]::new($true)`、前置 `"\uFEFF"` |
|
|
64
80
|
| Python `open(p, "w", encoding="utf-8-sig")` | 写 BOM;`"utf-8"` 不写;省略 `encoding` 走 locale |
|
|
81
|
+
| 模板引擎 / `sed` / `envsubst` / 程序内 replace | 产物编码由最后写入者决定,模板源正常不代表产物正常 |
|
|
65
82
|
| `.editorconfig` `charset` / git `.gitattributes` | 前者只在编辑器保存时生效;后者 `eol=` 管行尾、`working-tree-encoding=` 管编码,**都不补 BOM** |
|
|
66
83
|
|
|
67
84
|
⚠️ 两者同名不同义:要在两种 shell 下都跑,用 `[Text.UTF8Encoding]::new($true)` 显式指定;`utf8BOM` 只有 `pwsh` 认。
|
|
68
85
|
|
|
69
86
|
写完立刻自查(AI 写入工具默认无 BOM,是本类故障最常见来源):
|
|
70
87
|
|
|
71
|
-
1.
|
|
72
|
-
2.
|
|
73
|
-
3.
|
|
88
|
+
1. 新建或改写需要 BOM 的文件(如 `.ps1`)后判首三字节,不是 `EF BB BF` 当场补,别等运行时报错
|
|
89
|
+
2. 批量生成、打包、模板渲染、格式化钩子跑完后抽查产物并读回与原文比对,再回跑一次生成脚本——只补产物不修产线,下次还会丢
|
|
90
|
+
3. 不落盘的内联命令没有文件侧问题,运行期查「跑起来乱码」或「跨边界」
|
|
74
91
|
|
|
75
92
|
判工具:`powershell.exe` `[IO.File]::ReadAllBytes($p)[0..2] -join ','`(`239,187,191` 即有 BOM)、Node `fs.readFileSync(p).subarray(0,3).toString("hex")`(`efbbbf`)、bash `head -c 3 f | xxd -p`;行尾 `file f` 报 `with CRLF, LF line terminators` 即混用;末尾缺换行 `tail -c 1 f | xxd -p` 非 `0a` 即缺。
|
|
76
93
|
|
|
77
94
|
Node `readFileSync(p, "utf8")` 会保留 `\uFEFF`,解析前 `s.replace(/^\uFEFF/, "")`;补 BOM(内容已是 UTF-8)用 `[IO.File]::WriteAllText($p, [IO.File]::ReadAllText($p), [Text.UTF8Encoding]::new($true))`,Node 读回后前置 `"\uFEFF"`。
|
|
78
95
|
|
|
79
|
-
全仓巡检:`git ls-files
|
|
80
|
-
|
|
81
|
-
## 组合速查:文件字节类
|
|
96
|
+
全仓巡检:`git ls-files` 后按基线表筛出项目用到的敏感类型(如 `.ps1` / `.sh` / `.bat` / `.csv` / `.properties` / 按平台编码编译的源码)逐个判首三字节;行尾混用一次性捞全 `git ls-files -z | xargs -0 file | grep CRLF`。人眼抽查只用于验证产线,别当门禁。
|
|
82
97
|
|
|
83
|
-
|
|
84
|
-
| --- | --- | --- |
|
|
85
|
-
| `powershell.exe` 报 `Missing closing '}'` / `Unexpected token`,文件看着没问题 | `.ps1` 无 BOM,中文被按 ANSI 解码 | 转 UTF-8 with BOM,逻辑不变 |
|
|
86
|
-
| `.bat` / `.cmd` 首行就报命令不存在 | 文件带 BOM | 去 BOM,中文改由 `.ps1` 承担 |
|
|
87
|
-
| Linux 侧 `.sh` 报 `$'\r': command not found` | CRLF | 转 LF,`.gitattributes` 用 `eol=lf` 固化 |
|
|
88
|
-
| 仓库里是 LF,检出后 `.sh` 变 CRLF;已配 `eol=lf` 但 CI 仍报 `$'\r'` | `core.autocrlf=true` 在检出时转换;属性只约束后续写入,不回溯已提交内容 | `.gitattributes` 写 `eol=lf` 覆盖本机设置;存量执行 `git add --renormalize .` 后提交 |
|
|
89
|
-
| Excel 打开 CSV 乱码,或另存出的 CSV 进程序后乱码 | 打开侧无 BOM 按 ANSI 解码;另存侧 Excel 按系统 ANSI 写 | 打开侧加 BOM(Python 写用 `utf-8-sig`);导出选「CSV UTF-8(逗号分隔)」或入库前转 UTF-8 |
|
|
90
|
-
| `JSON.parse` 报 `Unexpected token` 但内容正常 | 首字节 `EF BB BF` | 去 BOM |
|
|
91
|
-
| XML 报声明与实际编码不符 | `encoding=` 写 GBK 而实际 UTF-8 | 统一实际字节为 UTF-8,别只改声明 |
|
|
92
|
-
| 同一文件同事机器正常、本机乱码 | 那台机器开了「Beta: 使用 Unicode UTF-8 提供全球语言支持」,系统 ANSI 已变 UTF-8 | 别依赖该设置,仍按基线写 BOM |
|
|
98
|
+
## 组合速查
|
|
93
99
|
|
|
94
|
-
|
|
100
|
+
高频三条,其余见 `references/troubleshooting.md`:
|
|
95
101
|
|
|
96
102
|
| 现象 | 根因 | 处置 |
|
|
97
103
|
| --- | --- | --- |
|
|
98
|
-
|
|
|
99
|
-
|
|
|
100
|
-
|
|
|
101
|
-
|
|
102
|
-
定位不可见字符:GNU grep `grep -nP '[\x{00A0}\x{200B}\x{3000}\x{FEFF}\x{200E}\x{200F}]' f`;`powershell.exe` `Select-String -Path f -Pattern '[\u00A0\u200B\u3000\uFEFF\u200E\u200F]'`。
|
|
104
|
+
| `powershell.exe` 报 `Missing closing '}'` / `Unexpected token`,文件看着没问题 | `.ps1` 无 BOM,非 ASCII 被按 ANSI 解码 | 转 UTF-8 with BOM,逻辑不变 |
|
|
105
|
+
| `.bat` / `.cmd` 首行就报命令不存在 | 文件带 BOM | 去 BOM,非 ASCII 改由 `.ps1` 承担 |
|
|
106
|
+
| Excel 打开 CSV 乱码 | 打开侧无 BOM 按 ANSI 解码 | 加 BOM(Python 写用 `utf-8-sig`)或导出选「CSV UTF-8」 |
|
|
103
107
|
|
|
104
108
|
## 跑起来乱码(不落盘)
|
|
105
109
|
|
|
@@ -107,8 +111,8 @@ Node `readFileSync(p, "utf8")` 会保留 `\uFEFF`,解析前 `s.replace(/^\uFEF
|
|
|
107
111
|
|
|
108
112
|
| 断点 | 现象 | 处置 |
|
|
109
113
|
| --- | --- | --- |
|
|
110
|
-
| 控制台代码页(简体默认 936) | 原生命令输出 UTF-8
|
|
111
|
-
| `powershell.exe` `$OutputEncoding` 默认 ASCII | PS
|
|
114
|
+
| 控制台代码页(简体默认 936) | 原生命令输出 UTF-8 非 ASCII 字符显示乱码 | `chcp 65001` + `[Console]::OutputEncoding = [Text.UTF8Encoding]::new()` |
|
|
115
|
+
| `powershell.exe` `$OutputEncoding` 默认 ASCII | PS 管道把非 ASCII 传给原生命令后变 `?` | 先设 `$OutputEncoding = [Text.UTF8Encoding]::new()` |
|
|
112
116
|
| `powershell.exe` `>` / `Out-File` 默认 UTF-16LE | 重定向出的 `.json` / `.csv` 后续解析失败 | 改 `-Encoding utf8` 或 `[IO.File]::WriteAllText`;`pwsh` 默认 UTF-8 无 BOM |
|
|
113
117
|
| `powershell.exe` `Get-Content` 对无 BOM 文件按 ANSI | 读 UTF-8 文件得乱码,误判文件损坏 | 显式 `-Encoding utf8`;`pwsh` 默认 UTF-8 |
|
|
114
118
|
| Node 输出被 `powershell.exe` 读 | 控制台 UTF-16 显示正常,落文件是 UTF-8,再读就乱 | 读侧显式编码,别改 Node 侧 |
|
|
@@ -118,8 +122,12 @@ Node `readFileSync(p, "utf8")` 会保留 `\uFEFF`,解析前 `s.replace(/^\uFEF
|
|
|
118
122
|
|
|
119
123
|
⚠️ 别因 stdout 乱码就回头改源文件;`file -i` 与 chardet 对短文本误判率高,只作参考。
|
|
120
124
|
|
|
125
|
+
## 跨边界(不落盘、不出本机)
|
|
126
|
+
|
|
127
|
+
非 ASCII 文本跨进程 / 跨机器传递时,边界同样按读取方默认解码判断;请求体、命令行参数、环境变量、模板产物都可能被 ASCII / ANSI 默认编码吃掉。写完后从目标端读回与原文比对,本地字符串正常不代表远端落对。构造方式、逐项处置与读回校验见 `references/boundaries.md`。
|
|
128
|
+
|
|
121
129
|
## 修复纪律
|
|
122
130
|
|
|
123
131
|
1. **消灭组合条件并固化**:能不带 BOM 的格式就别带;必须带的统一基线,写进 `.gitattributes`(`eol` / `working-tree-encoding`)、`.editorconfig`(`charset`)与项目文档
|
|
124
132
|
2. **机器校验**:生成后或 CI 断言首三字节与行尾,不靠人眼
|
|
125
|
-
3.
|
|
133
|
+
3. **只转一次、不只补产物**:转码前确认原始编码(有损不可逆,回源重取);BOM 反复丢失一定有一处在重写文件,先定位那次写入再补
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# 跨边界传递:命令行、环境变量、请求体、模板与远端元数据
|
|
2
|
+
|
|
3
|
+
> 本文件是 `fxri-text-encoding` 的明细附表;触发判据与乱码三问以 `SKILL.md` 为准。
|
|
4
|
+
|
|
5
|
+
## 通用判据
|
|
6
|
+
|
|
7
|
+
非 ASCII 文本每跨一次边界,就重新按目标端的默认解码检查一次:
|
|
8
|
+
|
|
9
|
+
1. **构造侧**:用 UTF-8 构造字节,不经过 ASCII / ANSI 默认的控制台、管道或字符串拼接。
|
|
10
|
+
2. **传输侧**:显式声明 UTF-8(请求头、文件编码、模板输出编码),不依赖工具默认值。
|
|
11
|
+
3. **落地侧**:写完从目标端读回,与原文逐字比对;本地字符串正常不代表远端落对。
|
|
12
|
+
4. **有损即回源**:出现字符缺失、替换、截断、空串时原字节已丢,不能反向修复,只能回源重取并修构造 / 传输通道。
|
|
13
|
+
|
|
14
|
+
## 边界与处置
|
|
15
|
+
|
|
16
|
+
| 边界 | 典型场景 | 风险 | 处置 |
|
|
17
|
+
| --- | --- | --- | --- |
|
|
18
|
+
| 命令行参数 | 原生程序、CLI 工具、`git` 参数 | Windows 原生程序可能按 ANSI / OEM 转换;旧程序只取单字节 | 非 ASCII 优先走文件或 stdin;必须走参数时先小样验证读回 |
|
|
19
|
+
| 环境变量 | CI 变量、启动脚本注入 | 子进程按自身默认编码读取,跨 shell 后失真 | 在 UTF-8 shell / `pwsh` 中设置;`powershell.exe` 下非 ASCII 变量优先落文件再读 |
|
|
20
|
+
| HTTP / RPC 请求体 | 项目描述、release、issue / PR 正文、Webhook | 客户端按 ASCII / ANSI 编码请求体;服务端或数据库再次转换、截断 | 请求体以 UTF-8 字节发送并声明 charset;写后 GET 读回比对 |
|
|
21
|
+
| 模板渲染 | 仓库模板、占位符替换、`sed` / `envsubst` / 程序内 replace | 模板源正常,产物由最后写入者决定 | 读写两端都显式 UTF-8;产物读回比对;不靠控制台默认编码 |
|
|
22
|
+
| git / 远端元数据 | tag 注释、项目描述、仓库设置 | CLI / API 默认编码不一致,远端二次处理 | 与请求体同法;提交信息的内容风格另见提交信息规则 |
|
|
23
|
+
|
|
24
|
+
## 构造示例
|
|
25
|
+
|
|
26
|
+
- `curl`:请求体先落 UTF-8 文件,再 `curl -sS -X PUT -H "Content-Type: application/json; charset=utf-8" --data-binary @body.json "$URL"`。
|
|
27
|
+
- `powershell.exe`:`$bytes = [Text.Encoding]::UTF8.GetBytes($json)`,`Invoke-RestMethod -Body $bytes -ContentType 'application/json; charset=utf-8'`;不要依赖字符串 `-Body` 的默认编码。
|
|
28
|
+
- Node:`Buffer.from(JSON.stringify(body), "utf8")` 作为 `fetch` 的 body;读回用同一编码解析。
|
|
29
|
+
- Python:`json.dumps(obj, ensure_ascii=False).encode("utf-8")` 作为请求体;不要省略 `ensure_ascii` 后又按字面量当正文。
|
|
30
|
+
- 模板:模板与产物都用 UTF-8 读写;替换后立即读回,确认占位符位置与原文一致。
|
|
31
|
+
|
|
32
|
+
## 读回校验
|
|
33
|
+
|
|
34
|
+
- 文件:判首三字节与行尾,再按 UTF-8 读取并与原文比对长度、字符。
|
|
35
|
+
- API / 远端:GET 回读,逐字比对;不要只判断“非空”或“包含关键字”。
|
|
36
|
+
- 服务端二次转换:数据库 `utf8`(非 `utf8mb4`)会截断 emoji,平台可能做规范化;读回时同时核对长度与码点。
|
|
37
|
+
|
|
38
|
+
## 不要误修
|
|
39
|
+
|
|
40
|
+
`\uXXXX`、百分号编码、HTML 实体、Base64 是合法表示形式,目标端接受就不动;只有目标端明确要求原文时才解码。孤立 `?` 是合法字符,不能作为有损编码的证据。
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# 乱码与字节异常速查
|
|
2
|
+
|
|
3
|
+
> 本文件是 `fxri-text-encoding` 的明细附表;触发判据与乱码三问以 `SKILL.md` 为准。
|
|
4
|
+
|
|
5
|
+
## 表现 → 类别 → 可逆性
|
|
6
|
+
|
|
7
|
+
判据不是字符长什么样,而是与原文 / 声明编码比对后,字符是否还在、能否反向还原。
|
|
8
|
+
|
|
9
|
+
| 表现(示例,非穷举) | 类别 | 可逆性 | 处置 |
|
|
10
|
+
| --- | --- | --- | --- |
|
|
11
|
+
| `涓枃` / `䏿–‡` / `ÖÐÎÄ` / `锟斤拷` | 错误解码(mojibake) | 可逆 | 按错误链路反向转码 |
|
|
12
|
+
| `?` / `�` / `\uFFFD` / `�` / 空白 / 空串 / 截断 / 每字一个占位 | 有损编码(目标字符集无法表示) | 不可逆 | 回源重取,修写入通道 |
|
|
13
|
+
| `\u4e2d\u6587` / `%E4%B8%AD` / `中` / Base64 | 表示形式(转义 / 编码) | 可逆 | 目标端接受就不动,按对应格式解码 |
|
|
14
|
+
| 开头 ``、BOM 被当成正文 | 形态错位 | 可修 | 去 BOM |
|
|
15
|
+
| `烫烫烫` / `屯屯屯` | 非编码问题(未初始化内存 / 调试填充) | — | 查代码,不是编码 |
|
|
16
|
+
|
|
17
|
+
反误伤:
|
|
18
|
+
|
|
19
|
+
- 非 ASCII 本身不是故障;UTF-8 下正常的日文、韩文、emoji、重音字符保持原样,不改写。
|
|
20
|
+
- 合法的表示形式(JSON 转义、URL 百分号编码、HTML 实体、Base64)按目标端约定处理,不擅自解码成原文。
|
|
21
|
+
- 孤立 `?` 可能只是正文内容,只有与原文比对确认成片替换后才判有损。
|
|
22
|
+
- 4 字节字符(emoji 等)写入 `utf8`(非 `utf8mb4`)或旧字符集时会被替换或截断,属字符集不支持,应换编码而非用 `?` 顶替。
|
|
23
|
+
|
|
24
|
+
## 文件字节类
|
|
25
|
+
|
|
26
|
+
| 现象 | 根因 | 处置 |
|
|
27
|
+
| --- | --- | --- |
|
|
28
|
+
| `powershell.exe` 报 `Missing closing '}'` / `Unexpected token`,文件看着没问题 | `.ps1` 无 BOM,非 ASCII 被按 ANSI 解码 | 转 UTF-8 with BOM,逻辑不变 |
|
|
29
|
+
| `.bat` / `.cmd` 首行就报命令不存在 | 文件带 BOM | 去 BOM,非 ASCII 改由 `.ps1` 承担 |
|
|
30
|
+
| Linux 侧 `.sh` 报 `$'\r': command not found` | CRLF | 转 LF,`.gitattributes` 用 `eol=lf` 固化 |
|
|
31
|
+
| 仓库里是 LF,检出后 `.sh` 变 CRLF;已配 `eol=lf` 但 CI 仍报 `$'\r'` | `core.autocrlf=true` 在检出时转换;属性只约束后续写入,不回溯已提交内容 | `.gitattributes` 写 `eol=lf` 覆盖本机设置;存量执行 `git add --renormalize .` 后提交 |
|
|
32
|
+
| Excel 打开 CSV 乱码,或另存出的 CSV 进程序后乱码 | 打开侧无 BOM 按 ANSI 解码;另存侧 Excel 按系统 ANSI 写 | 打开侧加 BOM(Python 写用 `utf-8-sig`);导出选「CSV UTF-8(逗号分隔)」或入库前转 UTF-8 |
|
|
33
|
+
| `JSON.parse` 报 `Unexpected token` 但内容正常 | 首字节 `EF BB BF` | 去 BOM |
|
|
34
|
+
| XML 报声明与实际编码不符 | `encoding=` 写 GBK 而实际 UTF-8 | 统一实际字节为 UTF-8,别只改声明 |
|
|
35
|
+
| 同一文件同事机器正常、本机乱码 | 那台机器开了「Beta: 使用 Unicode UTF-8 提供全球语言支持」,系统 ANSI 已变 UTF-8 | 别依赖该设置,仍按基线写 BOM |
|
|
36
|
+
| PHP 页面 `headers already sent` | `<?php` 前有 BOM 并被输出 | 去 BOM,文件保持 UTF-8 无 BOM + LF |
|
|
37
|
+
|
|
38
|
+
## 内容类
|
|
39
|
+
|
|
40
|
+
| 现象 | 根因 | 处置 |
|
|
41
|
+
| --- | --- | --- |
|
|
42
|
+
| 编译报「非法字符 / unexpected character」但该行看不见异常 | 不可见字符:NBSP `U+00A0`、全角空格 `U+3000`、零宽空格 `U+200B`、中段 BOM `U+FEFF`、双向控制 `U+200E/200F` | 定位后替换,见下方命令 |
|
|
43
|
+
| 代码报语法错,字符看着像英文标点 | 全角标点 `“”():,;-` | 全文替换为半角,检查输入法全角态 |
|
|
44
|
+
| Python 输出 `\u4e2d\u6587`、git 把中文路径显示成 `\346\226\207` | `json.dump` 默认 `ensure_ascii=True`;`core.quotepath` 默认开启 | 分别传 `ensure_ascii=False`、`git -c core.quotepath=false`,都不是乱码 |
|
|
45
|
+
|
|
46
|
+
定位不可见字符:GNU grep `grep -nP '[\x{00A0}\x{200B}\x{3000}\x{FEFF}\x{200E}\x{200F}]' f`;`powershell.exe` `Select-String -Path f -Pattern '[\u00A0\u200B\u3000\uFEFF\u200E\u200F]'`。
|