feat: 添加 GBK 编码自动检测支持,文件读写工具透明处理非 UTF-8 文件

新增 encoding.ts 核心模块实现三层编码检测(BOM → UTF-8 fatal → GBK 回退),
改造同步/异步读取路径和写入路径,使 FileReadTool/FileEditTool/FileWriteTool
能正确处理 GBK 编码文件。包含完整单元测试和 spec 文档。

Co-Authored-By: glm-5-turbo <zai-org@claude-code-best.win>
This commit is contained in:
claude-code-best
2026-05-10 20:50:12 +08:00
parent 6e1d3d8f47
commit 0ce8f7a1cb
22 changed files with 1728 additions and 121 deletions

View File

@@ -29,6 +29,7 @@ import { extractClaudeCodeHints } from 'src/utils/claudeCodeHints.js';
import { detectCodeIndexingFromCommand } from 'src/utils/codeIndexing.js';
import { isEnvTruthy } from 'src/utils/envUtils.js';
import { isENOENT, ShellError } from 'src/utils/errors.js';
import { decodeBuffer } from 'src/utils/encoding.js';
import { detectFileEncoding, detectLineEndings, getFileModificationTime, writeTextContent } from 'src/utils/file.js';
import { fileHistoryEnabled, fileHistoryTrackEdit } from 'src/utils/fileHistory.js';
import { truncate } from 'src/utils/format.js';
@@ -511,7 +512,8 @@ async function applySedEdit(
const encoding = detectFileEncoding(absoluteFilePath);
let originalContent: string;
try {
originalContent = await fs.readFile(absoluteFilePath, { encoding });
const rawBuffer = await fs.readFileBytes(absoluteFilePath);
originalContent = decodeBuffer(rawBuffer, encoding);
} catch (e) {
if (isENOENT(e)) {
return {

View File

@@ -34,6 +34,11 @@ import {
type LineEndingType,
readFileSyncWithMetadata,
} from 'src/utils/fileRead.js'
import {
detectEncoding,
decodeBuffer,
type FileEncoding,
} from 'src/utils/encoding.js'
import { formatFileSize } from 'src/utils/format.js'
import { getFsImplementation } from 'src/utils/fsOperations.js'
import { fetchSingleFileGitDiff, type ToolUseDiff } from 'src/utils/gitDiff.js'
@@ -202,13 +207,8 @@ export const FileEditTool = buildTool({
let fileContent: string | null
try {
const fileBuffer = await fs.readFileBytes(fullFilePath)
const encoding: BufferEncoding =
fileBuffer.length >= 2 &&
fileBuffer[0] === 0xff &&
fileBuffer[1] === 0xfe
? 'utf16le'
: 'utf8'
fileContent = fileBuffer.toString(encoding).replaceAll('\r\n', '\n')
const encoding: FileEncoding = detectEncoding(fileBuffer)
fileContent = decodeBuffer(fileBuffer, encoding).replaceAll('\r\n', '\n')
} catch (e) {
if (isENOENT(e)) {
fileContent = null
@@ -584,7 +584,7 @@ export const FileEditTool = buildTool({
function readFileForEdit(absoluteFilePath: string): {
content: string
fileExists: boolean
encoding: BufferEncoding
encoding: FileEncoding
lineEndings: LineEndingType
} {
try {