Skip to content

fix(skills): copy skill supporting files byte-faithfully - #2575

Merged
dyoshikawa merged 4 commits into
mainfrom
resolve-issue-2563-binary-companion-files
Aug 4, 2026
Merged

fix(skills): copy skill supporting files byte-faithfully#2575
dyoshikawa merged 4 commits into
mainfrom
resolve-issue-2563-binary-companion-files

Conversation

@dyoshikawa

@dyoshikawa dyoshikawa commented Aug 4, 2026

Copy link
Copy Markdown
Owner

Background

Part of #2563 — this PR takes only the companion-file byte-fidelity defect reported there; the issue stays open for the remaining shared-frontmatter question.

DirFeatureProcessor.writeAiDirs split a skill's supporting files (everything beside SKILL.md) into a text path and a binary path using isBinaryBuffer, which detects only content a UTF-8 round-trip would mangle. Everything else went through addTrailingNewline(file.fileBuffer.toString("utf-8")), so any supporting file that happens to be valid UTF-8 had its CRLF line endings flattened to LF, its trailing whitespace stripped, and a trailing newline appended. docs/reference/file-formats.md documents that Rulesync "preserves supporting files beside directory-layout SKILL.md", which that rewrite contradicts: a fixture whose exact bytes matter, a CSV that needs RFC 4180 CRLF, or a file whose missing trailing newline is deliberate came out different from the source.

Solution

Supporting files are now copied byte for byte, unconditionally — both the comparison and the write stay on the buffer path. SKILL.md itself is unchanged: rulesync composes its body and frontmatter, so it keeps its trailing-newline normalization and structured-equivalence comparison.

Change detection is byte-exact too, via a new companionFileContentsEquivalent, with one narrow exception. Not every companion is a user asset: Codex CLI's agents/openai.yaml is composed by rulesync from frontmatter, and comparing it byte-exactly would make every generate report the whole skill directory as changed once a formatter re-indented it. Such files are now flagged composed: true on AiDirFile, and only they fall back to the structured verdict of the existing comparison when the bytes differ. A carried-through user asset never does — whatever its extension, a drifted copy is repaired rather than tolerated, which is what "byte for byte" has to mean. There is deliberately no text fallback either: trailing-whitespace-insensitive text equality is exactly the normalization companion files no longer get.

Consequences:

  • The text/binary branch and its otherFileContents staging array are gone, which also removes the "synchronization issue between otherFiles and otherFileContents arrays" internal-error guard that only existed to keep the two arrays aligned.
  • isBinaryBuffer in src/utils/file.ts had this as its only caller and is removed with it.
  • A supporting file written by an older Rulesync (normalized), or edited in place by a formatter, is detected as changed and rewritten from the source on the next generate. That is documented in the new supporting-file note in docs/reference/file-formats.md.

Deliberately out of scope, to keep the PR to byte fidelity: symlink and executable-bit preservation.

Testing

  • Added dir-feature-processor tests: a CRLF, newline-less text companion is written byte-identical rather than normalized; an identical existing file is skipped; a file differing only in those trailing bytes is rewritten; a change in a later companion is still detected; a re-indented composed agents/openai.yaml counts as unchanged. The existing JPEG-buffer tests still cover the invalid-UTF-8 case.
  • Added unit tests for companionFileContentsEquivalent covering the binary, CRLF, composed-structured and carried-through-structured cases, and a real-filesystem test in skills-processor.test.ts that writes a PNG header and a CRLF fixture through writeAiDirs, reads the bytes back unchanged, and confirms a second write is a no-op.
  • npx vitest run --config vitest.e2e.config.ts src/e2e/e2e-skills.spec.ts — 130 passed.
  • pnpm cicheck — all green.

🤖 Generated with Claude Code

@dyoshikawa
dyoshikawa merged commit c26fb4d into main Aug 4, 2026
9 checks passed
@dyoshikawa
dyoshikawa deleted the resolve-issue-2563-binary-companion-files branch August 4, 2026 04:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants