Repository navigation
feat(cli): preserve original text encoding when editing files - #8587
Conversation
Introduce kilocode Encoding module and integrate into IO paths implement readEncoded and writeEncoded in filesystem for encoding-aware file IO propagate encoding through patching workflows (patch, apply_patch, edit, write) add tests for encoding utilities
| if (bytes.length >= 2 && bytes[0] === 0xfe && bytes[1] === 0xff) { | ||
| return { encoding: "utf-16be", bom: true } | ||
| } | ||
| if (bytes.length >= 2 && bytes[0] === 0xff && bytes[1] === 0xfe) { |
There was a problem hiding this comment.
WARNING: UTF-32 LE BOM is misdetected as UTF-16 LE
Files that start with FF FE 00 00 also satisfy this branch, so decode() / encode() will rewrite a UTF-32 file as UTF-16 and corrupt it. Check the 4-byte UTF-32 BOM before returning UTF-16 here, or reject unsupported UTF-32 explicitly so the tool does not silently rewrite it.
There was a problem hiding this comment.
Addressed in a289f94 — the UTF-16 LE BOM check now explicitly tests whether bytes 2–3 are both zero and skips the match if so, preventing the UTF-32 LE BOM (FF FE 00 00) from being returned as UTF-16 LE.
Code Review SummaryStatus: 5 Issues Found | Recommendation: Address before merge Overview
Fix these issues in Kilo Cloud Issue Details (click to expand)WARNING
Other Observations (not in diff)No additional issues found outside the diff. Files Reviewed (54 files)
Reviewed by gpt-5.4-20260305 · 2,559,141 tokens |
…tion Address review bot warning: FF FE 00 00 (UTF-32 LE BOM) was being misdetected as UTF-16 LE. Now checks whether the two bytes after FF FE are both zero and skips the UTF-16 LE match if so.
Check for 4-byte null patterns before the 2-byte UTF-16 heuristic to prevent UTF-32 LE/BE content from being misclassified.
Replace hand-rolled BOM/heuristic detection with jschardet (v3.1.4) for statistical encoding detection and iconv-lite (v0.7.2) for encode/decode. This adds support for Shift-JIS, EUC-JP, GB2312, Big5, EUC-KR, Windows-1251, KOI8-R, and all other encodings these libraries support. BOM detection is still handled explicitly for precise round-trip fidelity including UTF-32 LE/BE.
…non-Latin files ReadTool now uses Encoding.detect + iconv-lite to decode files in any supported encoding (Shift-JIS, GB2312, Big5, EUC-KR, etc.) instead of hardcoding UTF-8. The binary detection heuristic now checks encoding first so UTF-16 files with null bytes and CJK encoded files are no longer falsely rejected as binary.
02a819e to
227af07
Compare
…ation # Conflicts: # packages/opencode/src/tool/apply_patch.ts # packages/opencode/src/tool/edit.ts # packages/opencode/src/tool/read.ts # packages/opencode/src/tool/write.ts
Drop manual BOM parsing, UTF-32, and UTF-16-without-BOM detection: rely on TextDecoder + jschardet + iconv-lite which already handle BOM round-tripping for UTF-16 LE/BE. Move the encoding helpers and Effect wrappers into packages/opencode/src/kilocode/ so shared tool files only carry targeted kilocode_change markers.
Exercise Read/Write/Edit/ApplyPatch tools directly against files in UTF-8, UTF-16 LE/BE (with BOM), Shift_JIS, EUC-JP, GB2312, Big5, EUC-KR, Windows-1251, and KOI8-R to verify the tools decode input correctly and round-trip bytes back to the original encoding. Also ensure UTF-16 BOMs survive the write pipeline.
This reverts commit 227af07.
This reverts commit ce80c09.
- Restore the streaming createInterface loop in the `lines` helper so the diff against main stays small; decode bytes with iconv-lite up front and feed the resulting text through Readable.from. - Drop 'new file' kilocode_change markers from files under kilocode/ directories (those paths are exempt from the annotation check). - Tighten the changeset copy to sound like a user-facing release note.
iconv-lite's utf-8 codec strips any leading BOM on decode and never emits one on encode, so files that started with EF BB BF would lose their BOM after an edit. Track UTF-8-with-BOM as a distinct synthetic encoding label and re-prepend the BOM bytes on write so the round-trip keeps the file byte-identical (modulo any actual edits).
- Wire Readable.from through a named `stream` variable so the `lines` helper can destroy it in the finally block, matching the previous createReadStream pattern. - Restore the 'Create parent directories', 'Handle file move', 'Regular update', 'Read original file content', and 'For delete, we need to read the current content' comments that were dropped when switching to Encoding.write / Encoding.read. The explicit fs.mkdir calls are no longer needed because Encoding.write mkdirs recursively, but the intent comments still apply.
- Encoding.detect: lowercase-compare UTF-32 rejection so jschardet's
uppercase 'UTF-32'/'UTF-32BE'/'UTF-32LE' labels no longer slip through
(iconv-lite happens to have utf-32 codecs that would then be accepted).
- Encoding.read: drop redundant Buffer.from wrap around readFile's
already-Buffer return value.
- EncodedIO: switch to Effect.tryPromise so I/O failures surface as
typed errors that .pipe(Effect.catch(...)) can recover from; the
apply_patch delete branch relies on this to translate read errors
into 'apply_patch verification failed'.
- Shrink kilocode_change blocks in edit.ts, write.ts, apply_patch.ts,
patch/index.ts, and read.ts to per-line inline markers where each
block was only wrapping 1-3 changed lines.
- Collapse the 'if (exists) { let contentOld; let encoding; ... }'
blocks in edit.ts and write.ts into a single ternary + destructure
that keeps all three lines self-contained and each independently
annotated.
- Remove the unused 'latin1' sample and fix the 'providEncoded' typo
in the encoding integration test file.
- Strengthen the apply_patch Shift_JIS test to assert exact bytes
instead of the weak 'does not contain these two UTF-8 bytes' check.
- Drop the stray .kilo/ and .kilocode/ lockfiles that were re-added
by an upstream 'wip' commit.
Per review: CJK and legacy single-byte encodings already passed the old control-char heuristic (their bytes are all >= 0x80, never NUL), so the full Encoding.detect pass in isBinaryFile was solving a non-problem for them. The only realistic regression in the old heuristic is UTF-16 with BOM, where the second byte of every ASCII character is 0x00 and fires the NUL-byte early-return. Replace the detect call with a 2-byte BOM check via a new Encoding.hasUtf16Bom helper. Also reword the move/add inline markers in patch/index.ts to note that Encoding.write handles mkdir.
Main moved the Patch namespace out of packages/opencode/src/patch/index.ts into patch/patch.ts and switched tool imports to the new @opencode-ai/shared/filesystem and ../../src/tool barrels. Reapply the encoding-aware read/write changes on top of the refactored patch.ts, and update the integration test imports to match the new module layout.
- Drop the dead 'encoding = pre.encoding' initial value that was only read if deriveNewContentsFromChunks threw, and that code path returns before encoding is ever used. - Inline pre.text into the read line so we don't bind a 'pre' local just to destructure it once.
Strip a leading U+FEFF from text before prepending a UTF-8/UTF-16 BOM so round-tripping content that already carries a BOM character emits exactly one BOM. Also clarifies the changeset that new files default to UTF-8.
…ation # Conflicts: # packages/opencode/src/patch/patch.ts # packages/opencode/src/tool/edit.ts # packages/opencode/src/tool/write.ts
…tion feat(cli): preserve original text encoding when editing files
…tion feat(cli): preserve original text encoding when editing files
Summary
Changes
New files
packages/opencode/src/kilocode/encoding.ts—Encodingnamespace with detect/decode/encode/read/write functions. BOM detection is explicit; statistical detection delegates to jschardet; encode/decode uses iconv-litepackages/opencode/test/kilocode/encoding.test.ts— 34 tests covering BOM detection, CJK round-trips (Shift-JIS, EUC-JP, Big5, GB2312, EUC-KR), Cyrillic round-trips (Windows-1251, KOI8-R), and file I/O preservationModified files
packages/opencode/src/util/filesystem.ts— AddedreadEncoded()andwriteEncoded()helperspackages/opencode/src/tool/read.ts— Uses encoding-aware decoding so non-UTF-8 files display correctly; binary detection updated to not falsely flag UTF-16 or CJK-encoded filespackages/opencode/src/tool/edit.ts— Uses encoding-aware read/write to preserve file encoding during editspackages/opencode/src/tool/write.ts— Uses encoding-aware read/write to preserve existing file encoding when overwritingpackages/opencode/src/tool/apply_patch.ts— Uses encoding-aware read/write for all patch operationspackages/opencode/src/patch/index.ts—deriveNewContentsFromChunksnow returns encoding info;applyHunksToFilesuses encoding-aware writespackages/opencode/package.json— Added jschardet and iconv-lite dependenciesHow it works