feat(tarball): binary fetcher for zip archives (#437 slice C)#472
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds per-entry ignore filtering to archive extraction, extends TarballError for zip and path-traversal failures, implements zip fetch-and-extract pipeline with optional prefix-stripping and ignore-filtering, threads the new API through retry plumbing and callers, updates tests, and adds the zip dependency. ChangesArchive Filtering and Zip Support
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Pull request overview
Implements the zip-archive half of pnpm’s “binary fetcher” in pacquet-tarball, adding an in-memory download + integrity check + per-entry CAS extraction pipeline, and threads an optional per-entry ignore filter through both tarball and zip extraction to support Node runtime “extras” stripping.
Changes:
- Add zip extraction (
extract_zip_entries) plusDownloadZipArchiveToStorewith retry + progress/log emission parity to the tarball flow. - Thread
ignore_file_patternthroughDownloadTarballToStore/extract_tarball_entriesto support per-archive entry exclusion. - Add
zipas a workspace dependency and wire it intopacquet-tarball, updating call sites/tests/benchmarks for the newignore_file_patternfield.
Reviewed changes
Copilot reviewed 7 out of 8 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| tasks/micro-benchmark/src/main.rs | Updates benchmark harness to populate the new ignore_file_pattern field on tarball downloads. |
| crates/tarball/src/tests.rs | Adjusts tarball tests for new extraction signature and adds zip-extractor unit tests. |
| crates/tarball/src/lib.rs | Core implementation: zip fetch/extract pipeline, new error variants, and ignore-filter plumbing for tar + zip. |
| crates/tarball/Cargo.toml | Adds zip dependency usage to pacquet-tarball. |
| crates/package-manager/src/install_package_from_registry.rs | Updates tarball download call site to pass ignore_file_pattern: None. |
| crates/package-manager/src/install_package_by_snapshot.rs | Updates tarball download call site to pass ignore_file_pattern: None. |
| Cargo.toml | Adds zip = "5" to workspace dependencies (deflate-only, no default features). |
| Cargo.lock | Locks new dependency graph introduced by zip. |
Comments suppressed due to low confidence (2)
crates/tarball/src/tests.rs:2047
- This
dbg!(&cas_paths);debug print will spam test output. Please remove it to keep the test suite quiet.
dbg!(&cas_paths);
crates/tarball/src/tests.rs:2100
- This
dbg!(&cas_paths);debug print will spam test output. Please remove it to keep the test suite quiet.
dbg!(&cas_paths);
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #472 +/- ##
==========================================
- Coverage 89.61% 88.55% -1.06%
==========================================
Files 119 121 +2
Lines 11098 12234 +1136
==========================================
+ Hits 9945 10834 +889
- Misses 1153 1400 +247 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Micro-Benchmark ResultsLinux |
Integrated-Benchmark Report (Linux)Scenario: Frozen Lockfile
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 2.71904315346,
"stddev": 0.08947966145406333,
"median": 2.72895980536,
"user": 2.56474022,
"system": 3.7650946999999997,
"min": 2.5764544333600004,
"max": 2.84777590936,
"times": [
2.6282696863600004,
2.73613882836,
2.7286920303600004,
2.70207382836,
2.7650706103600005,
2.72922758036,
2.5764544333600004,
2.84777590936,
2.63048981336,
2.8462388143600004
]
},
{
"command": "pacquet@main",
"mean": 2.6555025690600003,
"stddev": 0.055439845464651964,
"median": 2.65779852136,
"user": 2.6021929199999994,
"system": 3.7749892000000003,
"min": 2.56258926936,
"max": 2.7545129143600002,
"times": [
2.62697032736,
2.5863430053600003,
2.56258926936,
2.6659934193600003,
2.6559302123600004,
2.65365311436,
2.7545129143600002,
2.68005690436,
2.65966683036,
2.7093096933600003
]
},
{
"command": "pnpm",
"mean": 6.28395022156,
"stddev": 0.053569903974164335,
"median": 6.27955170736,
"user": 9.34455502,
"system": 4.5714847999999995,
"min": 6.21835355036,
"max": 6.35725753336,
"times": [
6.23967632336,
6.29922734036,
6.35725753336,
6.21891111536,
6.24473755936,
6.21835355036,
6.25987607436,
6.32548814236,
6.3427025313600005,
6.33327204536
]
}
]
}Scenario: Frozen Lockfile (Hot Cache)
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 0.7575869656,
"stddev": 0.054434556490960975,
"median": 0.7466758209000001,
"user": 0.39364111999999996,
"system": 1.6048055599999995,
"min": 0.7088926524000001,
"max": 0.9052828304000001,
"times": [
0.9052828304000001,
0.7544362784,
0.7431580014000001,
0.7490591994000001,
0.7470446844,
0.7671136764,
0.7309489894000001,
0.7088926524000001,
0.7463069574000001,
0.7236263864000001
]
},
{
"command": "pacquet@main",
"mean": 0.7642349823000002,
"stddev": 0.06772233840693095,
"median": 0.7358048909000001,
"user": 0.37653141999999995,
"system": 1.62431146,
"min": 0.7093041234,
"max": 0.8987162884000001,
"times": [
0.7576539494000001,
0.7093041234,
0.8987162884000001,
0.7266937424000001,
0.7394712524000001,
0.7157820094,
0.8790623254000001,
0.7321385294,
0.7254050334000001,
0.7581225694000001
]
},
{
"command": "pnpm",
"mean": 2.6887758119,
"stddev": 0.1369849955210745,
"median": 2.6629462619,
"user": 3.2528337200000004,
"system": 2.2757651599999997,
"min": 2.5481384534,
"max": 3.0112050944,
"times": [
2.6981962094,
2.7531606224000003,
3.0112050944,
2.6276963144,
2.5794095204,
2.5481384534,
2.5749437494,
2.7215333184,
2.6119726174,
2.7615022194
]
}
]
} |
Address Copilot review on #472: - `extract_zip_entries` masks `entry.unix_mode().unwrap_or(0o644) & 0o777` before writing `CafsFileInfo.mode`, matching the permission-only convention enforced by `store_dir::add_files_from_dir::file_mode_from`. Without the mask, the high `st_mode` bits (e.g. `0o100000` for a regular file) would land in the `index.db` row and diverge from what pnpm writes for tarball entries / on-disk imports. - Adds `TarballError::ReadZipEntries(std::io::Error)` for zip per-entry I/O failures (try_reserve / read_to_end), distinct from `ReadTarballEntries`. The retry-classification path now emits `ERR_PACQUET_ZIP` rather than the misleading `ERR_PACQUET_TARBALL_TAR` for zip-side failures. - Changes `ignore_file_pattern` from `Option<&'static IgnoreEntryFilter>` to `Option<Arc<IgnoreEntryFilter>>` on both `DownloadTarballToStore` and `DownloadZipArchiveToStore`. The Slice D dispatcher can now construct one filter per fetch from runtime config (the per-package `archiveFilters` map upstream uses) without leaking the closure or pinning it to `'static`. Cloning the Arc per retry attempt is cheap; the inner trait object is shared.
Adds the zip-archive side of pnpm's binary fetcher. Mirrors [`downloadAndUnpackZip` and `extractZipToTarget`](https://github.com/pnpm/pnpm/blob/94240bc046/fetching/binary-fetcher/src/index.ts): walks zip entries, rejects absolute / `..` paths via `zip::ZipFile::enclosed_name`, strips the archive's top-level basename (`BinaryResolution::prefix`) so the ignore filter and CAS keys see the same relative paths upstream's regex does, then writes each file entry into the CAS through `StoreDir::write_cas_file`. * `extract_zip_entries` — parallel to `extract_tarball_entries`, with `archive_prefix` + `ignore_file_pattern` plumbing * `fetch_and_extract_zip_once` / `fetch_and_extract_zip_with_retry` — same network permit shape, retry policy, and post-download semaphore gating as the tarball path * `DownloadZipArchiveToStore` — sibling of `DownloadTarballToStore` with the same store-index lookup, prefetch cache reuse, and store-index writer queue * `ignore_file_pattern` threaded through `DownloadTarballToStore` and the tarball extractor (the tarball half of upstream's `archiveFilters` map) * Adds `zip = "5", default-features = false, features = ["deflate"]` to the workspace; reused in `pacquet-tarball` Includes 4 unit tests for the zip extractor: prefix-stripping, ignore-filter applied to the stripped path, `..` rejection, and the no-prefix passthrough. Each rejection guard was verified by temporarily disabling it and re-running the test. The install-dispatch site (slice D) and the per-package `ignoreFilePattern` value (the Node-runtime extras filter) will land in the follow-up PR.
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
crates/tarball/src/lib.rs (1)
1758-1759:⚠️ Potential issue | 🟠 Major | 🏗️ Heavy liftKey the cache/index by extraction shape, not just
(integrity, package_id).
ignore_file_patternandarchive_prefixnow change the persistedPackageFilesIndex, but both the lookup and write paths still usestore_index_key(&integrity, package_id)only. That means a filtered fetch can be reused for an unfiltered one (or vice versa), so callers can get the wrong file set out of the store without touching the network. The same collision also exists for the tarball mem-cache keyed only bypackage_url. Consider threading an explicit extraction-variant key/fingerprint from the caller into both read and write paths, or bypassing reuse when non-default extraction is requested.Also applies to: 1837-1839, 2153-2155, 2195-2197
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/tarball/src/lib.rs` around lines 1758 - 1759, The cache/index keys (created via store_index_key(&package_integrity.to_string(), package_id) in the read/write paths) must include the extraction “shape” fingerprint (e.g., ignore_file_pattern and archive_prefix) so filtered vs unfiltered PackageFilesIndex entries do not collide; update the functions that call store_index_key (and the tarball mem-cache keyed by package_url) to accept and incorporate an extraction_variant or fingerprint parameter provided by the caller (or compute a stable fingerprint from ignore_file_pattern + archive_prefix) and use that in both lookup and write paths, or alternatively short-circuit reuse when the extraction is non-default; ensure the same symbol (extraction_variant/fingerprint) is threaded into the code paths referenced around store_index_key, PackageFilesIndex write/read, and the tarball mem-cache to keep keys consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@crates/tarball/src/lib.rs`:
- Around line 699-706: The loop currently checks entry.is_dir() and continues
before validating the entry path, allowing malicious dir entries like "../evil/"
to bypass traversal checks; update the loop so you call entry.enclosed_name()
(or otherwise validate the path and return TarballError::PathTraversal with the
package URL and offending path) before skipping directory entries, and treat any
invalid or non-enclosed names as an error rather than silently continuing; apply
the same change to the other occurrence noted around the 713-719 range so every
entry (including directories) is validated prior to being skipped or processed.
---
Outside diff comments:
In `@crates/tarball/src/lib.rs`:
- Around line 1758-1759: The cache/index keys (created via
store_index_key(&package_integrity.to_string(), package_id) in the read/write
paths) must include the extraction “shape” fingerprint (e.g.,
ignore_file_pattern and archive_prefix) so filtered vs unfiltered
PackageFilesIndex entries do not collide; update the functions that call
store_index_key (and the tarball mem-cache keyed by package_url) to accept and
incorporate an extraction_variant or fingerprint parameter provided by the
caller (or compute a stable fingerprint from ignore_file_pattern +
archive_prefix) and use that in both lookup and write paths, or alternatively
short-circuit reuse when the extraction is non-default; ensure the same symbol
(extraction_variant/fingerprint) is threaded into the code paths referenced
around store_index_key, PackageFilesIndex write/read, and the tarball mem-cache
to keep keys consistent.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 76c27e6b-6cfe-4d99-9f9e-a56056fb22eb
📒 Files selected for processing (1)
crates/tarball/src/lib.rs
📜 Review details
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs
📄 CodeRabbit inference engine (AGENTS.md)
**/*.rs: Preserve existing method chains andpipe-traitchains; do not break them into intermediateletbindings unless there is a concrete justification such as a compilation failure, borrow checker rejection, meaningful performance improvement, or other technical necessity. Refactoring for style alone is not sufficient justification.
Choose owned vs. borrowed parameters to minimize copies; prefer borrowed types (&Pathover&PathBuf,&strover&String) when it does not force extra copies.
PreferArc::clone(&x)andRc::clone(&x)overx.clone()for reference-counted types to make the cost visible at the call site.
Do not use star imports inside module bodies. Writeuse super::{Foo, bar}instead ofuse super::*;for any glob whose target is a module you control. External-crate preludes (e.g.,use rayon::prelude::*;) and root-of-module re-exports (e.g.,pub use submodule::*;inlib.rs) are exceptions.
Follow Rust API Guidelines for naming, as documented in https://rust-lang.github.io/api-guidelines/naming.html.
Declare a newtype wrapper for any branded string type being ported from TypeScript pnpm. Do not collapse the brand into a plainStringor&str; give the type its own struct so misuse is a type error.
When porting branded string types where upstream TypeScript always validates before construction, validate in the Rust port too. Construct the wrapper only viaTryFrom<String>and/orFromStr; do not provide an infallible public constructor that takes an arbitrary string.
For branded string types where upstream TypeScript never validates (used purely for type-safety to prevent confusion between string slots), expose an infallibleFrom<String>andFrom<&str>constructor in the Rust wrapper.
When upstream TypeScript occasionally constructs a branded type without validation (via bareasassertion), add afrom_str_unchecked(or similarly named) constructor on the Rust side. Keep the validating constructor as well; `from_str_u...
Files:
crates/tarball/src/lib.rs
🧠 Learnings (1)
📚 Learning: 2026-05-07T23:19:08.272Z
Learnt from: KSXGitHub
Repo: pnpm/pacquet PR: 401
File: tasks/integrated-benchmark/src/work_env.rs:343-344
Timestamp: 2026-05-07T23:19:08.272Z
Learning: When reviewing Rust code in pnpm/pacquet for deprecated API usage, do not automatically treat `serde_saphyr::to_string` as deprecated. In `serde-saphyr` v0.0.25, `serde_saphyr::to_string` has no `#[deprecated]` attribute (the `#[deprecated]` later in `serde-saphyr-0.0.25/src/lib.rs` applies to a different function). Only flag `serde_saphyr::to_string` as deprecated if the resolved dependency version’s source shows `#[deprecated]` on that specific function.
Applied to files:
crates/tarball/src/lib.rs
Address Copilot review on #472: - `extract_zip_entries` masks `entry.unix_mode().unwrap_or(0o644) & 0o777` before writing `CafsFileInfo.mode`, matching the permission-only convention enforced by `store_dir::add_files_from_dir::file_mode_from`. Without the mask, the high `st_mode` bits (e.g. `0o100000` for a regular file) would land in the `index.db` row and diverge from what pnpm writes for tarball entries / on-disk imports. - Adds `TarballError::ReadZipEntries(std::io::Error)` for zip per-entry I/O failures (try_reserve / read_to_end), distinct from `ReadTarballEntries`. The retry-classification path now emits `ERR_PACQUET_ZIP` rather than the misleading `ERR_PACQUET_TARBALL_TAR` for zip-side failures. - Changes `ignore_file_pattern` from `Option<&'static IgnoreEntryFilter>` to `Option<Arc<IgnoreEntryFilter>>` on both `DownloadTarballToStore` and `DownloadZipArchiveToStore`. The Slice D dispatcher can now construct one filter per fetch from runtime config (the per-package `archiveFilters` map upstream uses) without leaking the closure or pinning it to `'static`. Cloning the Arc per retry attempt is cheap; the inner trait object is shared.
`extract_zip_entries` ran the `is_dir()` early-continue *before* `enclosed_name()` validation, so an archive carrying a directory entry like `../evil/` was silently skipped instead of surfacing `TarballError::PathTraversal`. Pacquet wouldn't have written that directory either way (only file entries take the CAS write path), but the contract is "reject any unsafe entry" — for tooling that inspects the error code. Move the path validation above the `is_dir()` check. Adds a unit test `extract_zip_rejects_directory_entry_with_parent_component` that catches the regression (verified by temporarily reverting the guard order). Caught by CodeRabbit on #472.
`[crate::BinaryResolution::prefix]` resolved against `pacquet_tarball` where `BinaryResolution` doesn't live — it's `pacquet_lockfile::BinaryResolution`. Switched both references to text + fully-qualified path so rustdoc under `-D warnings` (the CI Doc job) stops failing.
…nostic OOM error Address Copilot review on #472: - `extract_zip_entries` builds the canonical `cas_paths` / `pkg_files_idx` key from `enclosed_name()` rather than the raw `entry.name()`. `enclosed_name()` collapses `.` segments and is already path-traversal-checked, so the map key, the ignore-filter input, and the basename strip all see the same string. Cross-platform: rebuild from `Normal` components joined with `/` so a Windows host can't smuggle `\` separators into the key. - `TarballError::TarballTooLarge` display text changed from "Tarball at {url} ..." to "Archive at {url} ..." so the zip pipeline (which reuses `allocate_tarball_buffer`) doesn't surface a misleading "Tarball ..." message on OOM / oversize. Diagnostic code stays `pacquet_tarball::tarball_too_large` for variant identity. - `run_with_mem_cache` doc-blocks the "stable filter per URL" invariant: the mem cache keys on `package_url` alone (matching pnpm's `tarballCache`), so callers must keep the (URL, filter) relation functional. Tarball URLs encode (name, version, integrity) and `archiveFilters` is keyed by `pkg.name` upstream, so this naturally holds; the Slice D dispatcher follows the same table. - New unit tests: - `extract_tarball_applies_ignore_filter_dropping_entries_from_both_maps` covers the tarball-side filter path (the existing zip tests only exercised it through `extract_zip_entries`). - `extract_zip_normalizes_dot_segments_in_entry_paths` pins the `.`-segment collapse. Verified by temporarily falling back to `raw_name`.
`extract_tarball_entries` built the cleaned entry path through
`PathBuf::push` + `to_string_lossy()`, which uses the platform's
native separator. On Windows that produces keys like `bin\tool`,
diverging from:
- pnpm's string-based path layer (always `/`), so a shared
`index.db` would have non-byte-identical keys depending on which
tool wrote the row.
- `pacquet_store_dir::add_files_from_dir` (already uses
`format!("{relative_dir}/{name}")`), the existing tarball-fetcher
tests that look up `cas_paths["package.json"]` etc., and the
zip-side path normalization landed earlier in this slice.
- Any `ignore_file_pattern` regex / hand-coded matcher, all of
which expect `/` separators.
Replaced the `PathBuf::push` accumulator with a `Vec<String>` of
per-component `to_string_lossy()` results joined by `/`. The
validation loop (every component must be `Component::Normal`) is
unchanged, and on Unix the cleaned string is the same as before.
The Windows test failure that surfaced this was
`extract_tarball_applies_ignore_filter_dropping_entries_from_both_maps`
(added this slice) — the new test was the first to exercise a
multi-component tarball path on Windows.
Caught by Windows CI on #472.
Address Copilot review on #472: - `fetch_and_extract_zip_once` was hitting `client.get(package_url)` without resolving `AuthHeaders::for_url`, so a runtime archive hosted behind a token-protected proxy would 401 even when credentials were configured. Threaded `auth_headers: &AuthHeaders` through `DownloadZipArchiveToStore`, `fetch_and_extract_zip_with_retry`, and `fetch_and_extract_zip_once`, and attach the `authorization` header the same way the tarball path does. - `TarballError::ReadZipEntries` only wrapped `std::io::Error`, so the rendered error said "Failed to read zip entry: <io_err>" with no URL or entry path. The accompanying doc claims pacquet surfaces the entry path that triggered the failure (matching `ReadZipArchive` and `PathTraversal`), so promote the variant to a struct shape carrying `url` + `entry_path` + `source` and attach the cleaned path at both call sites (the `try_reserve` for the entry's payload and the `read_to_end`).
Summary
Slice C of #437 — the zip half of pnpm's binary fetcher. Slices A (lockfile types) and B (variant picker) have landed; this slice adds the per-archive download and CAS-extraction pipeline. The install-dispatch site (slice D) and the
--no-runtimeflag (slice E) will follow.Mirrors upstream's
downloadAndUnpackZip/extractZipToTarget:extract_zip_entrieswalks every entry. Path validation runs before theis_dir()early-skip so directory entries like../evil/still surfacePATH_TRAVERSAL. The canonicalcas_paths/pkg_files_idxkey is built fromenclosed_name()rebuilt asNormalcomponents joined with/—.segments collapse, separators are cross-platform consistent, and the ignore filter sees the same string the map is keyed by.DownloadZipArchiveToStoreis the public sibling ofDownloadTarballToStore— same store-index lookup, prefetch cache reuse, store-index writer queue. The retry policy, network-permit shape, and post-download semaphore gating exactly match the tarball path.ignore_file_patternisOption<Arc<IgnoreEntryFilter>>on bothDownloadTarballToStoreandDownloadZipArchiveToStore, so the Slice D dispatcher can construct a filter per fetch from runtime config without pinning to'static. Cloned per retry attempt; the inner trait object is shared.run_with_mem_cachedoc-blocks the "stable filter per URL" invariant: the cache keys solely onpackage_url(matching pnpm'starballCache), and callers keep the (URL, filter) relation functional. Upstream'sarchiveFiltersis keyed bypkg.name, and tarball URLs encode(name, version, integrity), so this naturally holds.TarballError::ReadZipEntries(std::io::Error)for zip per-entry I/O failures (try_reserve/read_to_end); maps toERR_PACQUET_ZIPin the retry classifier, distinct from the tar-specificERR_PACQUET_TARBALL_TAR. Zipunix_mode()is masked to0o777before write soCafsFileInfo.modestays permission-only (matchesstore_dir::add_files_from_dir::file_mode_from).TarballError::TarballTooLargedisplay generalized from "Tarball at …" to "Archive at …" so the zip pipeline's OOM path doesn't surface a misleading message.zip = "5", default-features = false, features = ["deflate"]to the workspace; reused inpacquet-tarball.Test plan
7 new unit tests (6 zip-extractor + 1 tarball ignore-filter). Each rejection / normalization guard was verified by temporarily disabling it and re-running the test (per the practice in
plans/TEST_PORTING.md):extract_zip_strips_prefix_from_entry_paths— prefix stripextract_zip_applies_ignore_filter_on_stripped_path— filter sees post-strip pathsextract_zip_rejects_parent_dir_component—PATH_TRAVERSALon file entry with..extract_zip_rejects_directory_entry_with_parent_component—PATH_TRAVERSALon directory entry with..(validation must run beforeis_dir()skip)extract_zip_normalizes_dot_segments_in_entry_paths—.segments collapse in the canonical keyextract_zip_uses_entry_path_when_no_prefix—archive_prefix: Nonekeeps entry names verbatimextract_tarball_applies_ignore_filter_dropping_entries_from_both_maps— tarball filter drops entries from bothcas_pathsandpkg_files_idx.filespacquet-tarballtests passjust readygreentaplo format --check,just dylint,cargo doc -D warningsgreenWritten by an agent (Claude Code, claude-opus-4-7).