


Build a Library That Reads and Writes Layered Image Files Without Losing Data
Use it when building a library for a binary layered image format. It keeps low-level parsing apart from the user API, reads sections in order, keeps unknown data, rebuilds layer groups, tests with read-write-read checks, and releases through automation.
To use this AI rule
## Install Only the Dependencies a Task Needs and Use the Project's Commands
Read the contributor documentation before changing code.
Use the repository's environment manager and task commands rather than invoking tools from an unrelated runtime.
Set up only the dependency groups required by the task:
- install the default development dependencies for parsing and tests;
- add the optional rendering group when compositing or rasterization is involved;
- include documentation groups only when building documentation;
- expect optional scientific or drawing packages to be unavailable on some new runtime and operating-system combinations.
Use the canonical commands to:
- build compiled extension modules and distribution artifacts;
- run the full test suite with coverage;
- run a single test by node identifier;
- run tests without coverage when measuring focused behavior;
- run linting, formatting, and static type checks;
- build the HTML documentation and inspect its documented output directory.
Do not treat optional rendering dependencies as core parsing requirements.
Report a clear installation instruction when an optional operation is requested without its dependencies.
## Keep Low-Level Binary Reading and Writing Separate from the User API
Keep low-level binary parsing separate from the high-level user API.
The low-level layer:
- reads and writes the raw binary format according to its specification;
- models headers, records, tagged blocks, descriptors, image resources, masks, channels, and compression data;
- exposes explicit `read(fp)` and `write(fp)` serialization methods;
- preserves unknown data that cannot yet be interpreted.
The high-level layer:
- wraps low-level document structures in ergonomic interfaces;
- reconstructs user-facing object trees from flat binary records;
- exposes layer types, effects, masks, rendering, and document operations;
- delays expensive parsing until data is accessed where the contract permits it.
Internal code should import concrete low-level definitions from their defining modules.
Public consumers should use the supported package exports.
Do not leak low-level representation details into public methods without a compatibility reason.
## Read and Write the File's Sections in Order and Keep Unknown Data
Treat section order, lengths, signatures, and numeric widths as part of the file-format contract.
A typical layered image file contains five sequential sections:
1. A fixed-size file header containing signature, version, dimensions, channels, and color mode.
2. Color-mode data such as indexed palettes.
3. Image resources such as color profiles, guides, and thumbnails.
4. Layer and mask information containing layer records, channel data, and tagged blocks.
5. Flattened composite image data.
Validate fixed header sizes such as 26 bytes when the specification requires them.
Read and write sections in order; do not infer boundaries from convenient in-memory objects.
Return the number of bytes written from serialization methods so callers can verify size accounting.
Preserve unknown payloads as bytes during a round trip rather than discarding them.
## Rebuild Layer Groups from the Flat List of Records
When the format stores a hierarchy as a flat list, interpret explicit boundary records rather than assuming adjacency alone.
Recognize group-start and group-end markers and rebuild parent-child relationships with a stack or equivalent verified state machine.
Test at least:
- ordinary records outside groups;
- one group containing multiple children;
- nested groups;
- empty groups;
- malformed or unbalanced boundary markers;
- preservation of original record order.
Keep the flat low-level record list available for serialization while exposing a tree through the high-level API.
Do not make tree reconstruction mutate unrelated binary records.
## Add Tagged Blocks Through a Registry and Keep Unsupported Ones Intact
Give binary structures a shared base contract with class-level reading and instance-level writing.
Compose complex structures recursively from small primitives.
Use typed data-class facilities consistently for:
- generated initializers;
- field validation;
- type hints;
- controlled representations;
- predictable tuple or sequence conversion where needed.
Keep validators consistent with annotations and use a same-instance return type for fluent or class-aware constructors.
For extensible tagged blocks:
- use a fixed-width tag key and associated payload;
- map known tags to handler classes through a registry;
- check for a tag before reading its typed data;
- keep unsupported blocks intact for later writing;
- separate public convenience access from internal record access.
Do not register a handler that consumes bytes it cannot faithfully write back.
## Speed Up Compression and Pixel Math Without Breaking the Fallback
Measure performance-sensitive paths before optimizing them.
Use compiled codecs for expensive compression or decompression when available, but retain a correct pure-language fallback.
Use lazy loading for masks, effects, and channel data when delayed parsing preserves semantics.
Use array vectorization for large pixel and blend-mode calculations where it improves throughput.
Every optimization must preserve:
- binary output compatibility;
- error behavior;
- deterministic results;
- the unoptimized fallback;
- support for environments where compiled or optional dependencies are unavailable.
Test optimized and fallback implementations against the same fixtures.
Do not silently change results to gain speed.
## Test Changes by Reading, Writing, and Rereading Files, and Add Type Hints
Organize tests to mirror the package structure, separating low-level binary tests from high-level API tests.
Keep representative binary fixtures in a dedicated fixture directory and parameterize across multiple files.
Use round-trip validation as a primary invariant:
1. Read the binary file.
2. Modify a controlled field.
3. Write the file.
4. Read the result again.
5. Verify the intended change and preservation of unrelated data.
Add proper type hints to every new function signature.
Keep data-class validators aligned with annotations.
Use the project's compatibility type for methods that return an instance of their own class.
Run focused tests during development, then the relevant full suite, linting, formatting, and type checking before completion.
Review coverage changes rather than using coverage percentage alone as proof of correctness.
## Release Through the Automated Workflow and Name Unsupported Features Clearly
Choose release versions according to the project's documented versioning standard, including final, prerelease, and post-release forms.
Summarize changes since the previous tag in the changelog under the new version heading.
Use the exact release-branch pattern required by automated tagging.
Open a release request against the main branch and merge only after approval.
Let the configured automation tag the exact merged commit, build distributions, create the hosted release, and publish to the package registry.
Do not create a manual tag when the release workflow owns tagging.
Ensure the automation credential has the least permissions needed to trigger downstream workflows, and never expose it.
Before claiming a feature works, check the documented limits for:
- text rendering when no font engine exists;
- supported and unsupported adjustment operations;
- the subset of implemented layer effects;
- extraction versus editing of embedded objects;
- preservation without interpretation of unknown data.
State unsupported behavior explicitly instead of approximating it silently.
Finish by reporting tests run, optional paths not exercised, compatibility risks, and any limitation that affects the requested change.
