feat: ignoredOptionalDependencies config + lockfile field + outdated-setting check (#434 slice 7)#507
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (12)
🚧 Files skipped from review as they are similar to previous changes (10)
📜 Recent review details⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
🧰 Additional context used📓 Path-based instructions (1)**/*.rs📄 CodeRabbit inference engine (AGENTS.md)
Files:
🧠 Learnings (3)📚 Learning: 2026-05-07T23:19:08.272ZApplied to files:
📚 Learning: 2026-05-13T22:52:32.579ZApplied to files:
📚 Learning: 2026-05-01T10:01:33.766ZApplied to files:
🔇 Additional comments (2)
📝 WalkthroughWalkthroughWires ignoredOptionalDependencies from workspace YAML into Config and top-level Lockfile, adds a lockfile-vs-config staleness check, applies an install-time ignore predicate to freshness comparisons, and updates tests/fixtures and current-lockfile preservation. ChangesignoredOptionalDependencies configuration and freshness support
Sequence Diagram(s)sequenceDiagram
participant Install
participant Lockfile
participant Config
participant CheckSettings
participant SatisfiesManifest
participant FlatSpecs
Install->>Lockfile: read ignored_optional_dependencies
Install->>Config: read ignored_optional_dependencies
Install->>CheckSettings: check_lockfile_settings(lockfile, config)
CheckSettings->>CheckSettings: normalize & compare sets
CheckSettings-->>Install: IgnoredOptionalDependenciesChanged on mismatch
Install->>SatisfiesManifest: satisfies_package_manifest(manifest, is_ignored_optional)
SatisfiesManifest->>FlatSpecs: flat_manifest_specs(manifest, is_ignored_optional)
FlatSpecs->>FlatSpecs: skip Prod/Optional entries matching predicate
FlatSpecs-->>SatisfiesManifest: filtered specifier map
SatisfiesManifest-->>Install: drift reason or success
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related issues
Possibly related PRs
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 docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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/lockfile/src/freshness.rs`:
- Around line 294-315: The predicate that filters out
ignoredOptionalDependencies is being applied to every DependencyGroup (including
Dev) during both the flat-spec pass (where manifest_prod/manifest_optional are
built) and the per-field pass inside the loop; restrict the
is_ignored_optional(name) filter so it only runs for DependencyGroup::Prod and
DependencyGroup::Optional (i.e., apply the filter when field ==
DependencyGroup::Prod || field == DependencyGroup::Optional and likewise when
building manifest_prod/manifest_optional) so devDependencies are not filtered,
and add a regression test that exercises adding/removing a dev-only dependency
that matches ignoredOptionalDependencies to ensure the freshness check now flags
drift correctly.
🪄 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: 9ff0b803-f1ff-4e79-a93d-23f52a4760ac
📒 Files selected for processing (12)
crates/config/src/lib.rscrates/config/src/workspace_yaml.rscrates/config/src/workspace_yaml/tests.rscrates/lockfile/src/freshness.rscrates/lockfile/src/freshness/tests.rscrates/lockfile/src/lib.rscrates/package-manager/src/current_lockfile.rscrates/package-manager/src/current_lockfile/tests.rscrates/package-manager/src/hoisted_dep_graph.rscrates/package-manager/src/install.rscrates/package-manager/src/install_package_from_registry/tests.rscrates/real-hoist/src/tests.rs
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
- GitHub Check: copilot-pull-request-reviewer
- GitHub Check: Lint and Test (macos-latest)
- GitHub Check: Lint and Test (ubuntu-latest)
- GitHub Check: Lint and Test (windows-latest)
- GitHub Check: Code Coverage
- GitHub Check: Run benchmark on ubuntu-latest
- GitHub Check: Run benchmark on ubuntu-latest
🧰 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/package-manager/src/install_package_from_registry/tests.rscrates/package-manager/src/current_lockfile/tests.rscrates/package-manager/src/hoisted_dep_graph.rscrates/config/src/workspace_yaml.rscrates/lockfile/src/lib.rscrates/package-manager/src/install.rscrates/package-manager/src/current_lockfile.rscrates/config/src/workspace_yaml/tests.rscrates/lockfile/src/freshness.rscrates/real-hoist/src/tests.rscrates/config/src/lib.rscrates/lockfile/src/freshness/tests.rs
🧠 Learnings (5)
📚 Learning: 2026-05-01T10:01:33.766Z
Learnt from: zkochan
Repo: pnpm/pacquet PR: 349
File: crates/reporter/src/tests.rs:121-121
Timestamp: 2026-05-01T10:01:33.766Z
Learning: In Rust test code, follow the repo’s CODE_STYLE_GUIDE test-logging rule: add logging (e.g., `eprintln!`/`eprintln!(...)`) so that useful diagnostic values are printed when a test fails, unless the assertion is `assert_eq!` (where the differing values are already included). Concretely, if you use assertions like `assert!`, `assert_ne!`, etc., ensure the test logs the relevant actual/expected values (or context) before/around the assertion so failures can be diagnosed without rerunning.
Applied to files:
crates/package-manager/src/install_package_from_registry/tests.rscrates/package-manager/src/current_lockfile/tests.rscrates/config/src/workspace_yaml/tests.rscrates/real-hoist/src/tests.rscrates/lockfile/src/freshness/tests.rs
📚 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/package-manager/src/install_package_from_registry/tests.rscrates/package-manager/src/current_lockfile/tests.rscrates/package-manager/src/hoisted_dep_graph.rscrates/config/src/workspace_yaml.rscrates/lockfile/src/lib.rscrates/package-manager/src/install.rscrates/package-manager/src/current_lockfile.rscrates/config/src/workspace_yaml/tests.rscrates/lockfile/src/freshness.rscrates/real-hoist/src/tests.rscrates/config/src/lib.rscrates/lockfile/src/freshness/tests.rs
📚 Learning: 2026-05-13T19:22:48.951Z
Learnt from: zkochan
Repo: pnpm/pacquet PR: 478
File: crates/package-manager/src/hoisted_dep_graph.rs:51-55
Timestamp: 2026-05-13T19:22:48.951Z
Learning: When reviewing this Rust codebase, avoid introducing/using a newtype like `PkgIdWithPatchHash` in only one module (e.g., `hoisted_dep_graph.rs`) if other related pacquet modules still represent the upstream pnpm “pkg id with patch hash” as a plain `String` (e.g., `virtual_store_layout`). For type consistency, either keep `pkg_id_with_patch_hash` as `String` here, or require a workspace-wide sweep that defines/extracts the `PkgIdWithPatchHash` newtype once and updates all call sites together (otherwise defer the refactor to a coordinated follow-up PR).
Applied to files:
crates/package-manager/src/install_package_from_registry/tests.rscrates/package-manager/src/current_lockfile/tests.rscrates/package-manager/src/hoisted_dep_graph.rscrates/package-manager/src/install.rscrates/package-manager/src/current_lockfile.rs
📚 Learning: 2026-05-13T20:09:22.171Z
Learnt from: zkochan
Repo: pnpm/pacquet PR: 486
File: crates/package-manager/src/hoisted_dep_graph.rs:472-476
Timestamp: 2026-05-13T20:09:22.171Z
Learning: In pnpm/pacquet, when generating serialized `hoisted_locations` strings (used for `.modules.yaml`), normalize path separators to forward slashes (e.g., replace `\\` with `/`). This preserves cross-platform portability: the output must not use OS-native separators because upstream pnpm’s `path.relative` can return platform-specific separators, which would break consistency with pnpm/pacquet’s other serialized formats on Windows.
Applied to files:
crates/package-manager/src/install_package_from_registry/tests.rscrates/package-manager/src/current_lockfile/tests.rscrates/package-manager/src/hoisted_dep_graph.rscrates/package-manager/src/install.rscrates/package-manager/src/current_lockfile.rs
📚 Learning: 2026-05-01T10:01:33.766Z
Learnt from: zkochan
Repo: pnpm/pacquet PR: 349
File: crates/reporter/src/tests.rs:121-121
Timestamp: 2026-05-01T10:01:33.766Z
Learning: In this repo’s Rust tests (run via `cargo nextest`), stdout/stderr from passing tests is captured and is only shown when a test fails. As a result, `eprintln!` (and similar stderr logging) in these tests should generally not be flagged as CI “noise” on the happy path; CI output should only appear for failing tests.
Applied to files:
crates/real-hoist/src/tests.rs
🔇 Additional comments (8)
crates/config/src/lib.rs (1)
492-507: LGTM!crates/package-manager/src/install_package_from_registry/tests.rs (1)
56-56: LGTM!crates/lockfile/src/lib.rs (1)
66-77: LGTM!crates/real-hoist/src/tests.rs (1)
37-37: LGTM!Also applies to: 65-65, 117-117, 169-169, 245-245, 315-315, 353-353, 404-404, 506-506, 588-588, 652-652, 727-727, 775-775, 827-827, 880-880, 930-930, 983-983, 1015-1015
crates/package-manager/src/current_lockfile/tests.rs (1)
59-59: LGTM!crates/package-manager/src/current_lockfile.rs (1)
86-91: LGTM!crates/package-manager/src/hoisted_dep_graph.rs (1)
863-863: LGTM!Also applies to: 879-879, 1333-1333, 1370-1370
crates/config/src/workspace_yaml.rs (1)
200-208: LGTM!Also applies to: 389-391
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #507 +/- ##
==========================================
+ Coverage 88.58% 88.60% +0.01%
==========================================
Files 125 125
Lines 13575 13643 +68
==========================================
+ Hits 12026 12088 +62
- Misses 1549 1555 +6 ☔ 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.11846057316,
"stddev": 0.06778529723526798,
"median": 2.1097511651599996,
"user": 2.7032178799999995,
"system": 2.05694058,
"min": 2.0000924736599996,
"max": 2.24177751066,
"times": [
2.24177751066,
2.1077433806599997,
2.14092371866,
2.0000924736599996,
2.11175894966,
2.09662391566,
2.18884373466,
2.05922901166,
2.15438695866,
2.08322607766
]
},
{
"command": "pacquet@main",
"mean": 2.11231005286,
"stddev": 0.03933720141454545,
"median": 2.12193385316,
"user": 2.7008383799999995,
"system": 2.06047258,
"min": 2.04437638966,
"max": 2.1485763796599997,
"times": [
2.1469331966599996,
2.1063183896599997,
2.1456741036599998,
2.1485763796599997,
2.04437638966,
2.09944506666,
2.13973509166,
2.1366376786599997,
2.10723002766,
2.04817420466
]
},
{
"command": "pnpm",
"mean": 5.243427424960001,
"stddev": 0.08976177694525828,
"median": 5.21307392516,
"user": 8.39048038,
"system": 2.59231218,
"min": 5.14900719366,
"max": 5.46331753766,
"times": [
5.31324026866,
5.19679286366,
5.21015602766,
5.14900719366,
5.226987981660001,
5.2133226736600005,
5.1782236606600005,
5.46331753766,
5.21282517666,
5.27040086566
]
}
]
}Scenario: Frozen Lockfile (Hot Cache)
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 0.5558121005400001,
"stddev": 0.06249411680986435,
"median": 0.53177887394,
"user": 0.3659983,
"system": 0.9496117999999999,
"min": 0.51699886094,
"max": 0.72118442094,
"times": [
0.72118442094,
0.53648856894,
0.52188493094,
0.54213588594,
0.54631475694,
0.5270691789399999,
0.52269691294,
0.51699886094,
0.5263044019399999,
0.59704308694
]
},
{
"command": "pacquet@main",
"mean": 0.61559833144,
"stddev": 0.06117589153944423,
"median": 0.6027643444399999,
"user": 0.37304930000000003,
"system": 0.9496848,
"min": 0.55172879194,
"max": 0.76797151294,
"times": [
0.76797151294,
0.61149114494,
0.62206280194,
0.59282798994,
0.55172879194,
0.62599410394,
0.56554233894,
0.64894817594,
0.59403754394,
0.57537890994
]
},
{
"command": "pnpm",
"mean": 2.08075560794,
"stddev": 0.13821696464840105,
"median": 2.0418527759400003,
"user": 2.708259799999999,
"system": 1.2360919999999997,
"min": 1.9104854789399999,
"max": 2.3642618049400004,
"times": [
2.26554318194,
2.0720135759400002,
2.0640107369400003,
2.0196948149400002,
1.97085514194,
1.9104854789399999,
2.0099632779400003,
2.3642618049400004,
2.1235556729400002,
2.0071723929400003
]
}
]
} |
…nal + set-based predicate CodeRabbit review on PR #507 (major): the filter wrongly applied to `devDependencies` too. Upstream's `hooks/read-package-hook/src/createOptionalDependenciesRemover.ts` iterates `manifest.optionalDependencies` and deletes matches from `optionalDependencies` AND `dependencies` only — `devDependencies` is untouched. The previous impl applied the filter to all three groups in both `flat_manifest_specs` and the per-field check, so a stale lockfile could incorrectly pass when the manifest added or removed a matching `devDependency`. Two fixes: 1. **Group gate** in `flat_manifest_specs` and the per-field check: apply the filter only when the group is `Prod` or `Optional`. `Dev` walks ignore the closure. 2. **Set-based predicate** at the call site (`Install::run`): build the "to drop" set from `manifest.optionalDependencies ∩ pattern`, not just from the pattern. A name listed only in `dependencies` (not `optionalDependencies`) that happens to match the pattern is NOT removed by upstream's hook (the hook never iterates that name). The set-based predicate captures that nuance. Both fixes together mirror the hook's exact semantics. Two new regression tests pin the dev-dependency behavior: `ignored_optional_does_not_apply_to_dev_dependencies` and `ignored_optional_dev_only_lockfile_entry_kept`. Test-the-test verified by dropping the group gate inside `flat_manifest_specs` — both tests fail.
…setting check (#434 slice 7) Last slice of the optional-dependencies umbrella (#434). Adds the user-facing `ignoredOptionalDependencies` setting: a list of dep-name patterns the user wants entirely excluded from resolution + install. Mirrors pnpm/pnpm@94240bc046's three surfaces: - **Hook**: `hooks/read-package-hook/src/createOptionalDependenciesRemover.ts` builds a matcher and drops matching keys from `optionalDependencies` AND `dependencies` (a package may list the same dep under both for "optional only when consumed"). - **Lockfile field**: `lockfile/types/src/index.ts:19` — `ignoredOptionalDependencies?: string[]` at the top level (sibling of `lockfileVersion`/`overrides`, NOT inside `settings`). - **Drift check**: `lockfile/settings-checker/src/getOutdatedLockfileSetting.ts:58-60` sorts both arrays and compares; mismatch triggers `needsFullResolution`. In pacquet's frozen-only flow this surfaces as `OutdatedLockfile`. ## Changes - **`pacquet-config`**: `Config::ignored_optional_dependencies: Option<Vec<String>>` + `WorkspaceSettings` field + `apply_to` wiring. - **`pacquet-lockfile`**: `Lockfile::ignored_optional_dependencies: Option<Vec<String>>` top-level field with serde round-trip; `check_lockfile_settings(lockfile, config_set)` sorts-and- compares; new `StalenessReason::IgnoredOptionalDependenciesChanged { lockfile, config }` variant. - **`pacquet-lockfile::satisfies_package_manifest`** extended with an `is_ignored_optional: &dyn Fn(&str) -> bool` parameter. Skips matching names in `flat_manifest_specs` and the per-field check so a manifest that still lists ignored entries doesn't falsely surface as drift against a lockfile the resolver correctly built without them. - **`pacquet-package-manager::install.rs`**: builds a matcher from `Config::ignored_optional_dependencies` (reuses `pacquet_config::matcher::create_matcher` — same glob engine as `hoistPattern`); calls `check_lockfile_settings` before `satisfies_package_manifest`; threads the matcher closure into the freshness check. - **`pacquet-package-manager::current_lockfile`**: preserves the lockfile's `ignored_optional_dependencies` through the slice 6 filter so the recorded set round-trips to the current lockfile. ## Tests - `pacquet_config`: yaml-parse + `apply_to` round-trip + omission baseline. - `pacquet_lockfile::freshness::check_settings_*`: both-sides- empty, sorted-match-regardless-of-order, drift in both directions. Test-the-test verified by removing the sort+compare guard. - `pacquet_lockfile::freshness::ignored_optional_filtered_*`: manifest-side filter passes when the matcher fires; polarity test confirms the unfiltered case surfaces as `SpecifiersDiffer`. - `pacquet_lockfile::freshness::ignored_optional_dependencies_round_trips_through_yaml`: serde wire-shape round-trip. Closes #503. Closes the #434 umbrella.
…nal + set-based predicate CodeRabbit review on PR #507 (major): the filter wrongly applied to `devDependencies` too. Upstream's `hooks/read-package-hook/src/createOptionalDependenciesRemover.ts` iterates `manifest.optionalDependencies` and deletes matches from `optionalDependencies` AND `dependencies` only — `devDependencies` is untouched. The previous impl applied the filter to all three groups in both `flat_manifest_specs` and the per-field check, so a stale lockfile could incorrectly pass when the manifest added or removed a matching `devDependency`. Two fixes: 1. **Group gate** in `flat_manifest_specs` and the per-field check: apply the filter only when the group is `Prod` or `Optional`. `Dev` walks ignore the closure. 2. **Set-based predicate** at the call site (`Install::run`): build the "to drop" set from `manifest.optionalDependencies ∩ pattern`, not just from the pattern. A name listed only in `dependencies` (not `optionalDependencies`) that happens to match the pattern is NOT removed by upstream's hook (the hook never iterates that name). The set-based predicate captures that nuance. Both fixes together mirror the hook's exact semantics. Two new regression tests pin the dev-dependency behavior: `ignored_optional_does_not_apply_to_dev_dependencies` and `ignored_optional_dev_only_lockfile_entry_kept`. Test-the-test verified by dropping the group gate inside `flat_manifest_specs` — both tests fail.
b5f696b to
34ed43d
Compare
Summary
Last slice of the #434 umbrella (
Proper optionalDependencies support). Adds the user-facingignoredOptionalDependenciessetting: a list of dep-name patterns the user wants entirely excluded from resolution + install.Mirrors three surfaces at pnpm/pnpm@94240bc046:
createOptionalDependenciesRemoverbuilds a matcher and drops matching keys fromoptionalDependenciesANDdependencies(a package may list the same dep under both).LockfileBase.ignoredOptionalDependencies?: string[]at the top level (sibling oflockfileVersion/overrides, not insidesettings).getOutdatedLockfileSetting.ts:58-60sorts both arrays and compares; mismatch flipsneedsFullResolution. Pacquet has no resolver, so the matching action isOutdatedLockfile.Changes
pacquet-config:Config::ignored_optional_dependencies: Option<Vec<String>>+WorkspaceSettingsfield +apply_towiring.pacquet-lockfile:Lockfile::ignored_optional_dependencies: Option<Vec<String>>top-level field with serde round-trip.check_lockfile_settings(lockfile, config_set)— sorts both sides and compares; sibling tosatisfies_package_manifest.StalenessReason::IgnoredOptionalDependenciesChanged { lockfile, config }variant.satisfies_package_manifestextended withis_ignored_optional: &dyn Fn(&str) -> boolso the manifest-side diff skips ignored names — matches what upstream's read-package-hook does at manifest-read time.pacquet-package-manager::install.rs: builds a matcher fromConfig::ignored_optional_dependencies(reusespacquet_config::matcher::create_matcher— same glob engine ashoistPattern), callscheck_lockfile_settingsbeforesatisfies_package_manifest, threads the matcher closure in.pacquet-package-manager::current_lockfile: preserves the wanted lockfile'signored_optional_dependenciesthrough the slice 6 filter so the recorded set round-trips into the current lockfile.Test plan
pacquet_config::workspace_yaml::tests::parses_ignored_optional_dependencies_from_yaml_and_applies+omitting_ignored_optional_dependencies_keeps_default.pacquet_lockfile::freshness::check_settings_*— both-empty, sorted-match, drift in both directions. Test-the-test verified by removing the sort+compare guard.pacquet_lockfile::freshness::ignored_optional_filtered_out_of_manifest_diff+ignored_optional_without_filter_surfaces_as_drift(polarity).pacquet_lockfile::freshness::ignored_optional_dependencies_round_trips_through_yaml.just ready(1158 tests pass) +taplo format --check+just dylint+RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --workspace.Out of scope
pacquet addremoving ignored entries frompackage.jsonat install time — not what upstream does either; the hook only filters what the resolver sees.Closes #503. Closes the #434 umbrella.
Written by an agent (Claude Code, claude-opus-4-7).
Summary by CodeRabbit
New Features
Bug Fixes
Tests