Skip to content

fix(deps-resolver): align pacquet peer parent contexts#12273

Merged
zkochan merged 8 commits into
mainfrom
fix/12272
Jun 9, 2026
Merged

fix(deps-resolver): align pacquet peer parent contexts#12273
zkochan merged 8 commits into
mainfrom
fix/12272

Conversation

@zkochan

@zkochan zkochan commented Jun 8, 2026

Copy link
Copy Markdown
Member

Summary

  • align pacquet peer parent context handling with pnpm for same-package child providers and peer diamonds
  • keep optional cached peer resolutions bubbling to later parents without an explicit provider, matching the jest-config and @types/node case
  • preserve pnpm's duplicated peer-suffix segments for aliased providers that resolve to the same package
  • include aliased child providers when their real package name is peer-relevant, not only when the install alias is peer-relevant
  • limit importer-seeded peer parent refs to alias/real names that can affect peer resolution, reducing clone overhead
  • add focused resolver tests for the issue 12272 lockfile mismatch and related diamond/alias-provider behavior
  • port the six related pnpm CLI alias-peer install cases to Pacquet CLI tests
  • pin the alias-peer CLI test peer suffix length so exact lockfile suffix assertions are independent of global config
  • update the Pacquet coverage upload to codecov-action v6.0.2 so it uses Codecov's current signing-key configuration

Fixes #12272.

Verification

  • cargo clippy --locked --workspace --all-targets -- -D warnings
  • RUSTFLAGS="-D warnings" cargo dylint --all -- --all-targets --workspace
  • cargo test --locked -p pacquet-resolving-deps-resolver
  • cargo test --locked -p pacquet-resolving-deps-resolver resolve_peers::tests::own_peer_is_resolved_from_aliased_child_real_name -- --nocapture
  • cargo test --locked -p pacquet-resolving-deps-resolver importer_parent_refs_skip_direct_deps_irrelevant_by_alias_and_real_name -- --nocapture
  • cargo test --locked -p pacquet-cli --test install peer_dependenc -- --nocapture
  • cargo test --locked -p pacquet-cli --test install -- --nocapture
  • cargo test --locked -p pacquet-cli --test install peer_shared_through_a_diamond_is_resolved_consistently -- --nocapture
  • pnpm --filter @pnpm/installing.deps-installer test test/install/peerDependencies.ts -t "in a subdependency, when there are several aliased dependencies of the same package"
  • cargo build --locked --bin pacquet
  • paired lockfile-only repro with jest@30.4.2, @babel/core@7.29.7, and autoInstallPeers: true; diff -u between pnpm and pacquet lockfiles exited 0
  • pre-push hook passed, including TS build/lint, spellcheck, Rust docs, dylint, and taplo checks

Written by an agent (Codex, GPT-5).

Summary by CodeRabbit

  • Bug Fixes

    • Improved peer dependency resolution in complex/diamond scenarios, across inherited vs own contexts, and when dependencies use npm-style aliases; prefers appropriate providers and higher alias versions. Better handling of optional peers when no provider exists.
  • Tests

    • Added end-to-end and unit tests for peer resolution, alias scenarios, inheritance vs own-context behavior, and edge cases; added a helper to run installs with aliased deps.
  • Chores

    • Updated Codecov action to v6.0.2.
  • Refactor

    • Reorganized peer-resolution internals for more consistent, deterministic outcomes.

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Threads explicit parent-node and parent-package context through resolve_peers, centralizes realized-vs-pending graph-child insertion, strengthens cache/parent matching (including optional peers and version-only compatibility), refactors parent-ref overwrite/version logic, adds tests for alias/diamond scenarios, and updates a CI workflow action.

Changes

Peer Resolution Core Refactor and Tests

Layer / File(s) Summary
Codecov workflow upgrade
.github/workflows/pacquet-codecov.yml
codecov/codecov-action updated to v6.0.2.
CLI install tests and helper
pacquet/crates/cli/tests/install.rs
Clarifies diamond-peer doc-comment; adds npm: alias peer-dependency e2e tests and install_with_peer_alias_deps helper.
Walk context threading and resolve_node wiring
pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs
resolve_node now receives explicit parent_node_ids and parent_pkg_ids_chain; Walker seeds per-importer parent contexts; purePkgs fast-path tightened by depth.
ParentRef construction and diamond/compat helpers
pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs
Rebuilds descendant-visible ParentRefs, adds parent_refs_match / inherited_parent_pkg_breaks_peer_diamond, refactors parent-ref insert/update/version logic, and changes ParentPkgInfo.version population.
Graph children realization & pending-edge handling
pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs
Adds previously_resolved_children and add_graph_child_or_pending; renames PendingPeerEdge fields to child_alias/child_node_id; defers missing peer targets into pending_peer_edges; patches pending edges using entry(...).or_insert(...); updates external peer reporting gate.
Cache matching and optional-peer tolerance
pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs
Introduces peer_dependency_is_optional, allows cache hits when cached peer providers are missing only if optional, and expands parent_packages_match to accept version-only compatibility.
DepPath normalization cleanup
pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs
Removes manual sorting/deduplication of peer ids before hashing in provisional and final depPath construction.
Resolve_peers unit tests and fixtures
pacquet/crates/resolving-deps-resolver/src/resolve_peers/tests.rs
Adds deterministic fixture builders and tests for inherited vs own peer resolution, aliased-child mapping, optional-peer bubbling, diamond conflict consistency, and previously_resolved_children selection.
Small cleanups and comments
pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs
Removes an allow(dead_code) annotation and updates inline comments/docstrings around pending-edge behavior and dual-keying.

Sequence Diagram(s)

sequenceDiagram
  participant CLI_Test
  participant Walker
  participant resolve_node
  participant add_graph_child_or_pending
  participant Lockfile

  CLI_Test->>Walker: start walk (seed parent refs)
  Walker->>resolve_node: resolve_node(parent_node_ids, parent_pkg_ids_chain)
  resolve_node->>add_graph_child_or_pending: insert child/peer or defer to pending_peer_edges
  add_graph_child_or_pending->>Lockfile: record realized edges / pending edges
  resolve_node->>Walker: return resolved node + ParentRefs
  Walker->>Lockfile: finalize pending edges and write pnpm-lock.yaml
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

  • pnpm/pnpm#12081: Implements related diamond-conflict checks and tests for consistent peer resolution.
  • pnpm/pnpm#11906: Related purePkgs fast-path and cache/compatibility changes in resolve_peers.rs.
  • pnpm/pnpm#12267: Overlapping work on pending-edge patching and depPath finalization in resolve_peers.rs.

Poem

"I hop through nodes with parent chains in tow,
I nudge pending edges where depPaths don't show.
With helpers stitched and tests that pry,
Diamond peers settle — no mixed supply.
A rabbit cheers: one graph, shared and low! 🐇"

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: aligning pacquet's peer parent-context handling with pnpm, which matches the core objective of fixing peer-resolution node-sharing and same-package shadowing.
Linked Issues check ✅ Passed The PR addresses all coding requirements from issue #12272: implementing name-keyed bubble gates, fixing node-sharing across parents via find_hit/parent_packages_match, handling same-package shadowing in cycles, ensuring aliased providers are considered correctly, and adding comprehensive tests.
Out of Scope Changes check ✅ Passed All changes align with stated objectives: resolve_peers.rs logic updates, new peer-resolution tests, install.rs CLI test cases for peer-alias scenarios, and codecov-action version update for coverage reporting consistency.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/12272

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Micro-Benchmark Results

Linux

group                          main                                   pr
-----                          ----                                   --
tarball/download_dependency    1.00      7.8±0.31ms   554.8 KB/sec    1.00      7.8±0.24ms   553.3 KB/sec

@zkochan zkochan marked this pull request as ready for review June 8, 2026 21:53
@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jun 8, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0)

Grey Divider


Action required

1. Aliased provider skipped ✓ Resolved 🐞 Bug ≡ Correctness
Description
In Walker::resolve_node, peer-provider ParentRefs are only added when the child’s install alias is
in all_peer_dep_names, so an npm-alias child whose real package name is peer-relevant can be omitted
entirely. This can incorrectly mark peers as missing and produce wrong depPaths/peer suffixes for
descendants.
Code

pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[R733-747]

for (alias, child_node_id) in &children_map {
+            if !self.tree.all_peer_dep_names.contains(alias) {
+                continue;
+            }
   let Some(child_tree) = self.tree.dependencies_tree.get(child_node_id) else { continue };
   let Some(child_pkg) = self.tree.packages.get(&child_tree.resolved_package_id) else {
       continue;
   };
-            let (child_real_name, child_version) = pkg_name_version(&child_pkg.result);
-            // Only peer-relevant aliases need to land in `parentRefs`.
-            // Pnpm filters with `allPeerDepNames` to keep the propagated
-            // map small.
-            let alias_relevant = self.tree.all_peer_dep_names.contains(alias);
-            let real_relevant = self.tree.all_peer_dep_names.contains(&child_real_name);
-            if !alias_relevant && !real_relevant {
-                continue;
-            }
-            let parent_ref = ParentRef {
-                version: child_version,
-                node_id: Some(child_node_id.clone()),
-                alias: (alias != &child_real_name).then(|| alias.clone()),
-                depth: child_depth,
-                occurrence: 0,
-            };
-            if alias_relevant {
-                bump_occurrence_on_shadow(&mut child_parent_refs, alias, &parent_ref);
-            }
-            if real_relevant && alias != &child_real_name {
-                bump_occurrence_on_shadow(&mut child_parent_refs, &child_real_name, &parent_ref);
+            insert_parent_ref(
+                &mut new_parent_refs,
+                alias,
+                child_node_id.clone(),
+                child_pkg,
+                child_tree.depth,
+            );
Evidence
The code documents that ParentRefs should be keyed by both alias and real name, but the new
resolve_node filter checks only the install alias, so aliased children can be dropped before
insert_parent_ref has a chance to add the real-name key.

pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[352-357]
pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[729-748]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`Walker::resolve_node` builds `new_parent_refs` from `children_map`, but it currently skips a child unless the **install alias** is in `all_peer_dep_names`. For npm-alias dependencies where the **real package name** is peer-relevant but the alias is not, the provider never makes it into `ParentRefs` (neither under alias nor real name), breaking peer resolution.
### Issue Context
- `ParentRefs` is meant to be dual-keyed by alias and real name (when they differ), so peers can be satisfied by either key.
- The current loop filters by `alias` before it even computes/considers the child’s real name.
### Fix Focus Areas
- pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[733-767]
### Implementation notes
- In the `for (alias, child_node_id) in &children_map` loop:
- Load `child_pkg` (already done).
- Compute `child_real_name` via `pkg_name_version(&child_pkg.result).0`.
- Change the early-continue condition to:
- `continue` only if **neither** `all_peer_dep_names.contains(alias)` **nor** `all_peer_dep_names.contains(&child_real_name)`.
- Then call `insert_parent_ref(...)` as today (it already writes both alias + real-name entries).
- Add/adjust a focused unit test covering: alias name NOT in `all_peer_dep_names` while real name IS, and a descendant peer on the real name resolves successfully.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Per-occurrence depth lost 🐞 Bug ≡ Correctness
Description
insert_parent_ref now stores ParentRef.depth from DependenciesTreeNode.depth, but
DependenciesTreeNode.depth is explicitly minimized across all visits (it is not the current walk’s
occurrence depth). parent_packages_match uses this depth (via ParentPkgInfo) as part of its
shadowing/cache-compatibility guard, so distinct occurrences can be treated as compatible and reuse
cached peer resolutions under the wrong context, producing incorrect depPaths/graph output.
Code

pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[R615-619]

 };
 let parent_node_id = remap_link_node_id(&self.opts, &direct.alias, &pkg.result)
     .unwrap_or_else(|| direct.node_id.clone());
-            insert_parent_ref(&mut refs, &direct.alias, parent_node_id, pkg, self.tree);
+            insert_parent_ref(&mut refs, &direct.alias, parent_node_id, pkg, tree_node.depth);
}
Evidence
The code documents ParentRef.depth as the depth where the parent was added, and threads it into
ParentPkgInfo specifically so parent_packages_match can apply a depth-based fallback when peers
are shadowed. This PR now supplies that depth from DependenciesTreeNode.depth, but the dependency
tree intentionally stores the *minimum* depth observed for a NodeId across visits, so the stored
depth can differ from the current occurrence depth and undermine the cache/shadowing guard.

pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[324-349]
pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[607-620]
pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[731-767]
pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[1632-1656]
pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[1682-1726]
pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[1756-1791]
pacquet/crates/resolving-deps-resolver/src/resolve_dependency_tree.rs[1173-1196]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`ParentRef.depth` is documented as “depth at which this parent was added” and is used downstream in `parent_packages_match` to decide whether a cached peer-resolution is compatible when shadowing (`occurrence > 0`) exists.
This PR now populates `ParentRef.depth` using `DependenciesTreeNode.depth` (`tree_node.depth` / `child_tree.depth`). But `DependenciesTreeNode.depth` is maintained as the *minimum depth across all occurrences* of that `NodeId`, which means it can be *shallower* than the actual occurrence depth for the current walk.
That breaks the intent of the depth-based guard in `parent_packages_match` and can allow (or deny) cache hits incorrectly, leading to incorrect peer-context reuse and wrong depPaths/graph output.
## Issue Context
`DependenciesTreeNode.depth` is intentionally “Math.min” across visits, so it’s not suitable as a per-occurrence depth signal.
## Fix Focus Areas
- pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[599-820]
- pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[1632-1792]
- pacquet/crates/resolving-deps-resolver/src/resolve_dependency_tree.rs[1173-1196]
### Concrete fix direction
- Reintroduce a recursion-local `current_depth` (like the previous `depth` parameter) or compute it from the current call chain length, and use that value when constructing `ParentRef.depth`.
- Importer parents should use depth `0` (or the same semantics as before).
- Child-derived parents should use `parent_depth + 1`.
- Avoid using `DependenciesTreeNode.depth` for `ParentRef.depth` (it’s a global minimum).
- Keep `DependenciesTreeNode.depth` as-is for install ordering/graph node depth; just decouple it from peer-context depth tracking.
- Add/adjust a resolver unit test that creates the same parent package reachable at two depths and asserts that cache matching/shadowing behaves differently across the two occurrences (i.e., depth is not “flattened” by the global min depth).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Aliased child peers over-bubble ✓ Resolved 📎 Requirement gap ≡ Correctness
Description
External peer bubbling is filtered using children_map.contains_key(peer_alias), but peer_alias
is keyed by the peer dependency name (real package name). For npm-aliased children where install
alias differs from real name, this fails to absorb the peer at the child node and can reintroduce
spurious peer-suffix propagation.
Code

pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[R848-851]

+                if children_map.contains_key(&peer_alias) {
       continue;
   }
   external_from_children.insert(peer_alias, peer_node_id);
Evidence
PR Compliance ID 1 requires name-keyed absorption of resolved peers when the node has a child with
the same package alias/name. The new code filters bubbling by checking
children_map.contains_key(peer_alias), but peers are recorded under peer_name in
resolve_one_peer, so a child installed under an npm alias will not match and the peer can
incorrectly bubble upward.

Align pacquet peer “bubble gate” behavior with pnpm (name-keyed absorption of peers listed as regular deps)
pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[838-852]
pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[1029-1032]
pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[1045-1086]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new peer absorption/bubbling filter checks only whether `children_map` contains the peer key (`children_map.contains_key(peer_alias)`), but peer keys are inserted using the peer dependency name (`peer_name`). This breaks name-keyed absorption when a child is present under an npm alias (install alias != real package name), allowing that peer to bubble upward.
## Issue Context
Compliance requires name-keyed absorption: if a node has a child with the same package alias/name, a resolved peer of that name must be considered internal and not bubbled. `children_map` is keyed by install alias, while peer resolution keys are `peer_name` (real name).
## Fix Focus Areas
- pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[838-852]
- pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[1029-1032]
- pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[1045-1086]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (3)
4. Wrong cycle child precedence ✓ Resolved 🐞 Bug ≡ Correctness
Description
previously_resolved_children() iterates ancestors closest-first (iter().rev()) but merges with
BTreeMap::extend(), so later (farther) ancestors can overwrite the closest ancestor’s child NodeId
for the same alias. Since children edges are per-occurrence, this can record an incorrect dependency
edge and change the final graph/lockfile output in repeated-package ancestry cases.
Code

pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[R1471-1491]

+    fn previously_resolved_children(
+        &mut self,
+        parent_node_ids: &[NodeId],
+        parent_pkg_ids_chain: &[String],
+        current_pkg_id: &str,
+    ) -> BTreeMap<String, NodeId> {
+        let mut children = BTreeMap::new();
+        if !parent_pkg_ids_chain.iter().any(|pkg_id| pkg_id == current_pkg_id) {
+            return children;
+        }
+        for parent_node_id in parent_node_ids.iter().rev() {
+            let same_pkg = self
+                .tree
+                .dependencies_tree
+                .get(parent_node_id)
+                .is_some_and(|node| node.resolved_package_id == current_pkg_id);
+            if same_pkg {
+                children.extend(self.realize_children(parent_node_id));
+            }
+        }
+        children
Evidence
Children are explicitly per-occurrence, so overwriting the closest occurrence’s child edge with a
farther occurrence’s edge can produce a dependency edge that doesn’t match the current occurrence.
The merged children are then used when building the dependency graph and lockfile snapshot
dependencies, so a wrong edge changes outputs.

pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[1471-1491]
pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[923-931]
pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[968-973]
pacquet/crates/resolving-deps-resolver/src/resolved_tree.rs[108-112]
pacquet/crates/package-manager/src/dependencies_graph_to_lockfile.rs[615-623]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`previously_resolved_children()` merges children from multiple same-package ancestors using `BTreeMap::extend()` while iterating ancestors in reverse order (closest-first). This allows farther ancestors to overwrite the closest ancestor’s child mapping for the same alias, which is risky because dependency-tree children are per-occurrence and may differ.
## Issue Context
The merged children set is used to build `graph_children` / `record_edges`, which ultimately drives snapshot dependencies written into the lockfile.
## Fix Focus Areas
- pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[1471-1491]
## Suggested fix
Change the merge semantics so the closest same-package ancestor wins on alias collisions:
- Option A (minimal): keep `iter().rev()` but insert with `entry().or_insert(...)` instead of `extend(...)` so earlier (closest) values are not overwritten.
- Option B: iterate from farthest-to-closest (remove `.rev()`) so that closer ancestors overwrite farther ones.
Add/adjust a focused unit test that constructs a repeated-package ancestry where two same-package ancestors have different per-occurrence children for the same alias, and assert that the chosen child comes from the closest ancestor.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Aliased child peers over-bubble ✓ Resolved 📎 Requirement gap ≡ Correctness
Description
External peer bubbling is filtered using children_map.contains_key(peer_alias), but peer_alias
is keyed by the peer dependency name (real package name). For npm-aliased children where install
alias differs from real name, this fails to absorb the peer at the child node and can reintroduce
spurious peer-suffix propagation.
Code

pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[R848-851]

+                if children_map.contains_key(&peer_alias) {
                   continue;
               }
               external_from_children.insert(peer_alias, peer_node_id);
Evidence
PR Compliance ID 1 requires name-keyed absorption of resolved peers when the node has a child with
the same package alias/name. The new code filters bubbling by checking
children_map.contains_key(peer_alias), but peers are recorded under peer_name in
resolve_one_peer, so a child installed under an npm alias will not match and the peer can
incorrectly bubble upward.

Align pacquet peer “bubble gate” behavior with pnpm (name-keyed absorption of peers listed as regular deps)
pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[838-852]
pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[1029-1032]
pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[1045-1086]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new peer absorption/bubbling filter checks only whether `children_map` contains the peer key (`children_map.contains_key(peer_alias)`), but peer keys are inserted using the peer dependency name (`peer_name`). This breaks name-keyed absorption when a child is present under an npm alias (install alias != real package name), allowing that peer to bubble upward.
## Issue Context
Compliance requires name-keyed absorption: if a node has a child with the same package alias/name, a resolved peer of that name must be considered internal and not bubbled. `children_map` is keyed by install alias, while peer resolution keys are `peer_name` (real name).
## Fix Focus Areas
- pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[838-852]
- pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[1029-1032]
- pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[1045-1086]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Wrong cycle child precedence ✓ Resolved 🐞 Bug ≡ Correctness
Description
previously_resolved_children() iterates ancestors closest-first (iter().rev()) but merges with
BTreeMap::extend(), so later (farther) ancestors can overwrite the closest ancestor’s child NodeId
for the same alias. Since children edges are per-occurrence, this can record an incorrect dependency
edge and change the final graph/lockfile output in repeated-package ancestry cases.
Code

pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[R1471-1491]

+    fn previously_resolved_children(
+        &mut self,
+        parent_node_ids: &[NodeId],
+        parent_pkg_ids_chain: &[String],
+        current_pkg_id: &str,
+    ) -> BTreeMap<String, NodeId> {
+        let mut children = BTreeMap::new();
+        if !parent_pkg_ids_chain.iter().any(|pkg_id| pkg_id == current_pkg_id) {
+            return children;
+        }
+        for parent_node_id in parent_node_ids.iter().rev() {
+            let same_pkg = self
+                .tree
+                .dependencies_tree
+                .get(parent_node_id)
+                .is_some_and(|node| node.resolved_package_id == current_pkg_id);
+            if same_pkg {
+                children.extend(self.realize_children(parent_node_id));
+            }
+        }
+        children
Evidence
Children are explicitly per-occurrence, so overwriting the closest occurrence’s child edge with a
farther occurrence’s edge can produce a dependency edge that doesn’t match the current occurrence.
The merged children are then used when building the dependency graph and lockfile snapshot
dependencies, so a wrong edge changes outputs.

pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[1471-1491]
pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[923-931]
pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[968-973]
pacquet/crates/resolving-deps-resolver/src/resolved_tree.rs[108-112]
pacquet/crates/package-manager/src/dependencies_graph_to_lockfile.rs[615-623]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`previously_resolved_children()` merges children from multiple same-package ancestors using `BTreeMap::extend()` while iterating ancestors in reverse order (closest-first). This allows farther ancestors to overwrite the closest ancestor’s child mapping for the same alias, which is risky because dependency-tree children are per-occurrence and may differ.
## Issue Context
The merged children set is used to build `graph_children` / `record_edges`, which ultimately drives snapshot dependencies written into the lockfile.
## Fix Focus Areas
- pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[1471-1491]
## Suggested fix
Change the merge semantics so the closest same-package ancestor wins on alias collisions:
- Option A (minimal): keep `iter().rev()` but insert with `entry().or_insert(...)` instead of `extend(...)` so earlier (closest) values are not overwritten.
- Option B: iterate from farthest-to-closest (remove `.rev()`) so that closer ancestors overwrite farther ones.
Add/adjust a focused unit test that constructs a repeated-package ancestry where two same-package ancestors have different per-occurrence children for the same alias, and assert that the chosen child comes from the closest ancestor.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

7. parent_packages_match version check too broad ✓ Resolved 📎 Requirement gap ≡ Correctness
Description
parent_packages_match now treats a parent-context mismatch as a version-only comparison whenever
either side has a version, which can reject otherwise shareable cache hits and lead to unnecessary
node/depPath variants across parents. This risks diverging from pnpm’s node sharing behavior and
reintroducing multi-variant outcomes (e.g., mixed-scope jest-config / @types/node cases).
Code

pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[R1773-1778]

+            // Version-only match covers `link:` parents whose nodeIds
+            // don't index into the dependencies tree.
+            if cached_info.version.is_some() || current_info.version.is_some() {
             if cached_info.version == current_info.version {
                 continue;
             }
Evidence
Rule 2/4 require pnpm-equivalent reuse/sharing across parents to avoid unnecessary variant
duplication. The changed condition `if cached_info.version.is_some() ||
current_info.version.is_some()` forces version comparison in more cases than just
link:/version-only parents, which can incorrectly fail parentPackagesMatch and prevent cache
hits needed for depPath reuse.

Implement pnpm-equivalent node sharing across parents (depPath reuse / find_hit parity)
Prevent @types/node divergence by ensuring shared variant selection matches pnpm under mixed ancestor scopes
pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[1773-1778]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`parent_packages_match` performs a version-only comparison when either `cached_info.version` or `current_info.version` is set. This can cause false cache misses in mixed cases (one side has `pkg_id`, the other only `version`), reducing depPath reuse and parent sharing.
## Issue Context
Version-only matching is intended to cover `link:` parents where the `NodeId` does not index into the dependencies tree. Mixed `pkg_id`/`version` cases should not automatically fall into the version-only branch if it prevents pnpm-equivalent sharing.
## Fix Focus Areas
- pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[1773-1778]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


8. Non-hermetic lockfile suffix asserts 🐞 Bug ☼ Reliability
Description
The new install_with_peer_alias_deps() helper appends workspace settings but does not pin
peersSuffixMaxLength, while the new tests assert exact peer-suffix strings in pnpm-lock.yaml.
Because pacquet layers global config.yaml before pnpm-workspace.yaml, a developer’s global
config can change suffix rendering (including hashing when max length is small) and make these
contains(...) assertions fail.
Code

pacquet/crates/cli/tests/install.rs[R929-952]

+fn install_with_peer_alias_deps(dependencies: serde_json::Value) -> String {
+    let CommandTempCwd { pacquet, root, workspace, npmrc_info, .. } =
+        CommandTempCwd::init().add_mocked_registry();
+    let AddMockedRegistry { mock_instance, .. } = npmrc_info;
+
+    let workspace_yaml_path = workspace.join("pnpm-workspace.yaml");
+    let mut workspace_yaml =
+        fs::read_to_string(&workspace_yaml_path).expect("read pnpm-workspace.yaml");
+    if !workspace_yaml.ends_with('\n') {
+        workspace_yaml.push('\n');
+    }
+    workspace_yaml.push_str("autoInstallPeers: false\n");
+    workspace_yaml.push_str("strictPeerDependencies: false\n");
+    fs::write(&workspace_yaml_path, workspace_yaml).expect("write pnpm-workspace.yaml");
+
+    fs::write(
+        workspace.join("package.json"),
+        serde_json::json!({ "dependencies": dependencies }).to_string(),
+    )
+    .expect("write package.json");
+
+    pacquet.with_arg("install").assert().success();
+    let lockfile =
+        fs::read_to_string(workspace.join("pnpm-lock.yaml")).expect("read pnpm-lock.yaml");
Evidence
install_with_peer_alias_deps() currently only appends autoInstallPeers and
strictPeerDependencies, while the tests assert exact peer-suffix strings from the lockfile.
pacquet applies global config.yaml before pnpm-workspace.yaml, and global config is allowed to
set peersSuffixMaxLength (it is not cleared as a workspace-only field). If peersSuffixMaxLength
is small, create_peer_dep_graph_hash hashes long suffixes, changing the lockfile text and breaking
contains(...) assertions.

pacquet/crates/cli/tests/install.rs[929-956]
pacquet/crates/config/src/lib.rs[1585-1702]
pacquet/crates/config/src/workspace_yaml.rs[576-610]
pacquet/crates/deps-path/src/create_peer_dep_graph_hash.rs[24-33]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The integration-test helper `install_with_peer_alias_deps()` writes `pnpm-workspace.yaml` but does not set `peersSuffixMaxLength`. The new tests match literal peer-suffix strings in `pnpm-lock.yaml`; if a developer/CI environment has a global pnpm `config.yaml` that sets a small peers-suffix max length, the suffix may be hashed and the assertions will fail.
### Issue Context
- pacquet config layering loads global `config.yaml` and applies it before `pnpm-workspace.yaml`, so global config can influence lockfile rendering.
- `create_peer_dep_graph_hash` replaces the suffix body with a short hash when it exceeds `max_length`.
### Fix Focus Areas
- pacquet/crates/cli/tests/install.rs[929-942]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


9. ParentRefs clone overhead ✓ Resolved 🐞 Bug ➹ Performance
Description
The importer ParentRefs is seeded with every direct dependency and then cloned per visited node,
even though only peer-relevant names are used for parent-context snapshots. This creates avoidable
HashMap cloning/allocations that can hurt performance in large dependency graphs.
Code

pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[R1806-1837]

direct_alias: &str,
parent_node_id: NodeId,
pkg: &ResolvedPackage,
-    tree: &ResolvedTree,
+    depth: i32,
) {
let (real_name, version) = pkg_name_version(&pkg.result);
-    let alias_relevant = tree.all_peer_dep_names.contains(direct_alias);
-    let real_relevant = tree.all_peer_dep_names.contains(&real_name);
-    if !alias_relevant && !real_relevant {
-        return;
-    }
let parent_ref = ParentRef {
version,
node_id: Some(parent_node_id),
alias: (direct_alias != real_name).then(|| direct_alias.to_string()),
-        depth: 0,
+        depth,
occurrence: 0,
};
-    if alias_relevant {
-        refs.insert(direct_alias.to_string(), parent_ref.clone());
+    update_parent_refs(refs, direct_alias, &parent_ref);
+    if direct_alias != real_name {
+        update_parent_refs(refs, &real_name, &parent_ref);
+    }
+}
+
+fn update_parent_refs(refs: &mut ParentRefs, new_alias: &str, parent_ref: &ParentRef) {
+    if let Some(existing) = refs.get(new_alias) {
+        let existing_has_alias = existing.alias.as_deref().is_some_and(|alias| alias != new_alias);
+        if !existing_has_alias {
+            return;
+        }
+        let new_has_alias = parent_ref.alias.as_deref().is_some_and(|alias| alias != new_alias);
+        if new_has_alias && version_gte(&existing.version, &parent_ref.version) {
+            return;
+        }
}
-    if real_relevant && direct_alias != real_name {
-        refs.insert(real_name, parent_ref);
+    refs.insert(new_alias.to_string(), parent_ref.clone());
+}
Evidence
Importer parents are populated by iterating all direct deps and calling insert_parent_ref, while
resolve_node clones ParentRefs per node; meanwhile, parent_dep_paths_from_refs explicitly ignores
non-peer-relevant names, indicating extra ParentRefs entries are overhead rather than functional
inputs.

pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[607-620]
pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[729-733]
pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[1624-1633]
pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[1804-1837]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`ParentRefs` is cloned (`parent_parent_refs.clone()`) as part of normal traversal. After this PR, `build_importer_parents_from` seeds `ParentRefs` for **all** direct dependencies via `insert_parent_ref`/`update_parent_refs` without any peer-relevance gating, even though downstream snapshots (`parent_dep_paths_from_refs`) explicitly filter to `all_peer_dep_names`. This can lead to unnecessary cloning/heap work proportional to `(visited nodes) × (direct deps)`.
### Issue Context
- `parent_dep_paths_from_refs` already filters to peer-relevant names, so non-peer-relevant direct deps don’t contribute to `parent_pkgs_of_node`/cache matching.
- Keeping `ParentRefs` minimal reduces cloning/merging work without changing resolution semantics for peers.
### Fix Focus Areas
- pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[607-621]
- pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[729-733]
- pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[1624-1633]
- pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[1804-1837]
### Implementation notes
- In `build_importer_parents_from`, before calling `insert_parent_ref`, compute `real_name` from `pkg_name_version(&pkg.result).0` and only insert if `all_peer_dep_names.contains(&direct.alias) || all_peer_dep_names.contains(&real_name)`.
- (Optional) Consider adding similar gating where importer parents are constructed in `resolve_peers_workspace` too, if applicable.
- This keeps correctness but avoids inflating `ParentRefs` with entries that will never be consulted for peer resolution.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jun 8, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📎 Requirement gaps (1)

Context used

Grey Divider


Action required

1. Aliased child peers over-bubble 📎 Requirement gap ≡ Correctness
Description
External peer bubbling is filtered using children_map.contains_key(peer_alias), but peer_alias
is keyed by the peer dependency name (real package name). For npm-aliased children where install
alias differs from real name, this fails to absorb the peer at the child node and can reintroduce
spurious peer-suffix propagation.
Code

pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[R848-851]

+                if children_map.contains_key(&peer_alias) {
                    continue;
                }
                external_from_children.insert(peer_alias, peer_node_id);
Evidence
PR Compliance ID 1 requires name-keyed absorption of resolved peers when the node has a child with
the same package alias/name. The new code filters bubbling by checking
children_map.contains_key(peer_alias), but peers are recorded under peer_name in
resolve_one_peer, so a child installed under an npm alias will not match and the peer can
incorrectly bubble upward.

Align pacquet peer “bubble gate” behavior with pnpm (name-keyed absorption of peers listed as regular deps)
pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[838-852]
pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[1029-1032]
pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[1045-1086]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new peer absorption/bubbling filter checks only whether `children_map` contains the peer key (`children_map.contains_key(peer_alias)`), but peer keys are inserted using the peer dependency name (`peer_name`). This breaks name-keyed absorption when a child is present under an npm alias (install alias != real package name), allowing that peer to bubble upward.

## Issue Context
Compliance requires name-keyed absorption: if a node has a child with the same package alias/name, a resolved peer of that name must be considered internal and not bubbled. `children_map` is keyed by install alias, while peer resolution keys are `peer_name` (real name).

## Fix Focus Areas
- pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[838-852]
- pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[1029-1032]
- pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[1045-1086]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Wrong cycle child precedence 🐞 Bug ≡ Correctness
Description
previously_resolved_children() iterates ancestors closest-first (iter().rev()) but merges with
BTreeMap::extend(), so later (farther) ancestors can overwrite the closest ancestor’s child NodeId
for the same alias. Since children edges are per-occurrence, this can record an incorrect dependency
edge and change the final graph/lockfile output in repeated-package ancestry cases.
Code

pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[R1471-1491]

+    fn previously_resolved_children(
+        &mut self,
+        parent_node_ids: &[NodeId],
+        parent_pkg_ids_chain: &[String],
+        current_pkg_id: &str,
+    ) -> BTreeMap<String, NodeId> {
+        let mut children = BTreeMap::new();
+        if !parent_pkg_ids_chain.iter().any(|pkg_id| pkg_id == current_pkg_id) {
+            return children;
+        }
+        for parent_node_id in parent_node_ids.iter().rev() {
+            let same_pkg = self
+                .tree
+                .dependencies_tree
+                .get(parent_node_id)
+                .is_some_and(|node| node.resolved_package_id == current_pkg_id);
+            if same_pkg {
+                children.extend(self.realize_children(parent_node_id));
+            }
+        }
+        children
Evidence
Children are explicitly per-occurrence, so overwriting the closest occurrence’s child edge with a
farther occurrence’s edge can produce a dependency edge that doesn’t match the current occurrence.
The merged children are then used when building the dependency graph and lockfile snapshot
dependencies, so a wrong edge changes outputs.

pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[1471-1491]
pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[923-931]
pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[968-973]
pacquet/crates/resolving-deps-resolver/src/resolved_tree.rs[108-112]
pacquet/crates/package-manager/src/dependencies_graph_to_lockfile.rs[615-623]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`previously_resolved_children()` merges children from multiple same-package ancestors using `BTreeMap::extend()` while iterating ancestors in reverse order (closest-first). This allows farther ancestors to overwrite the closest ancestor’s child mapping for the same alias, which is risky because dependency-tree children are per-occurrence and may differ.

## Issue Context
The merged children set is used to build `graph_children` / `record_edges`, which ultimately drives snapshot dependencies written into the lockfile.

## Fix Focus Areas
- pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[1471-1491]

## Suggested fix
Change the merge semantics so the closest same-package ancestor wins on alias collisions:
- Option A (minimal): keep `iter().rev()` but insert with `entry().or_insert(...)` instead of `extend(...)` so earlier (closest) values are not overwritten.
- Option B: iterate from farthest-to-closest (remove `.rev()`) so that closer ancestors overwrite farther ones.

Add/adjust a focused unit test that constructs a repeated-package ancestry where two same-package ancestors have different per-occurrence children for the same alias, and assert that the chosen child comes from the closest ancestor.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment thread pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs
Comment thread pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs
@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jun 8, 2026

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 6703bbf

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jun 8, 2026

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 185b70a

@codecov-commenter

codecov-commenter commented Jun 8, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.02899% with 22 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.52%. Comparing base (c199198) to head (6cb8034).
⚠️ Report is 25 commits behind head on main.

Files with missing lines Patch % Lines
...rates/resolving-deps-resolver/src/resolve_peers.rs 92.02% 22 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #12273      +/-   ##
==========================================
+ Coverage   87.38%   87.52%   +0.13%     
==========================================
  Files         278      280       +2     
  Lines       32506    33428     +922     
==========================================
+ Hits        28406    29258     +852     
- Misses       4100     4170      +70     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jun 8, 2026

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit e021c38

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jun 8, 2026

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 4baed62

@zkochan

zkochan commented Jun 8, 2026

Copy link
Copy Markdown
Member Author

Addressed Qodo’s ParentRefs clone overhead performance item in 4baed628c2. build_importer_parents_from() now only seeds importer ParentRefs when the direct dependency is peer-relevant by install alias or real package name, while keeping the existing dual-key insertion for aliases. I added importer_parent_refs_skip_direct_deps_irrelevant_by_alias_and_real_name to cover the optimization and reran the resolver suite plus the six ported Pacquet CLI alias-peer tests.


Written by an agent (Codex, GPT-5).

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jun 8, 2026

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit d682d8e

@zkochan

zkochan commented Jun 8, 2026

Copy link
Copy Markdown
Member Author

Addressed Qodo’s Non-hermetic lockfile suffix asserts reliability item in d682d8eb4c. The alias-peer install-test helper now writes peersSuffixMaxLength: 1000 into pnpm-workspace.yaml, alongside autoInstallPeers: false and strictPeerDependencies: false, so exact peer-suffix assertions are not affected by a smaller global config value.

Verification:

  • cargo fmt --all -- --check
  • cargo test --locked -p pacquet-cli --test install peer_dependenc -- --nocapture
  • pre-push hook passed after the commit

Written by an agent (Codex, GPT-5).

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jun 8, 2026

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 6cb8034

@zkochan

zkochan commented Jun 8, 2026

Copy link
Copy Markdown
Member Author

Addressed Qodo’s parent_packages_match version-check item in 6cb80349e3. The behavior was already equivalent to pnpm for mixed pkgId/version-only contexts, but the Rust branch now mirrors upstream parentPackagesMatch more directly: it only does a version-only comparison when both recorded contexts are version-only; mixed contexts fall through to the package-id comparison and miss, matching pnpm.

Verification:

  • cargo fmt --all -- --check
  • cargo test --locked -p pacquet-resolving-deps-resolver
  • pre-push hook passed after the commit

Written by an agent (Codex, GPT-5).

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Integrated-Benchmark Report (Linux)

Each scenario reports direct installs and pnpr installs. Bencher consumes pacquet@HEAD and pnpr@HEAD.

Scenario: Isolated linker: fresh restore, cold cache + cold store

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 9.965 ± 0.094 9.866 10.182 1.85 ± 0.04
pacquet@main 9.954 ± 0.127 9.852 10.287 1.85 ± 0.05
pnpr@HEAD 5.374 ± 0.112 5.254 5.584 1.00
pnpr@main 5.435 ± 0.107 5.316 5.594 1.01 ± 0.03
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 9.96498368688,
      "stddev": 0.09442034696101408,
      "median": 9.936733990079999,
      "user": 3.1659704999999994,
      "system": 3.2708764599999993,
      "min": 9.86640831858,
      "max": 10.18248369358,
      "times": [
        10.18248369358,
        10.01064769658,
        9.895579377579999,
        9.912326689579999,
        9.89602512758,
        9.86640831858,
        9.905495606579999,
        10.03437047558,
        9.985358592579999,
        9.961141290579999
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 9.954153732879998,
      "stddev": 0.127020992347452,
      "median": 9.921254166579999,
      "user": 3.1563049999999997,
      "system": 3.25501196,
      "min": 9.85229180058,
      "max": 10.28682649158,
      "times": [
        9.85229180058,
        10.28682649158,
        9.87505845458,
        9.973148450579998,
        10.01240101458,
        9.940172262579999,
        9.908293516579999,
        9.934214816579999,
        9.892396568579999,
        9.866733952579999
      ]
    },
    {
      "command": "pnpr@HEAD",
      "mean": 5.37440098418,
      "stddev": 0.11229110234310656,
      "median": 5.343192414080001,
      "user": 2.5686077,
      "system": 2.9290221599999997,
      "min": 5.25382981758,
      "max": 5.58419732858,
      "times": [
        5.35334493758,
        5.29555773458,
        5.33303989058,
        5.3683616045800004,
        5.58419732858,
        5.25382981758,
        5.3725801975800005,
        5.56797045358,
        5.29988125358,
        5.31524662358
      ]
    },
    {
      "command": "pnpr@main",
      "mean": 5.4353086706800005,
      "stddev": 0.10703705141182497,
      "median": 5.4261475490799995,
      "user": 2.5760514000000003,
      "system": 2.91699866,
      "min": 5.31556498758,
      "max": 5.59433370058,
      "times": [
        5.31556498758,
        5.45727788858,
        5.34184067058,
        5.54056840058,
        5.39501720958,
        5.4858767555800005,
        5.33231085458,
        5.59433370058,
        5.32639706358,
        5.5638991755800005
      ]
    }
  ]
}

Scenario: Isolated linker: fresh restore, hot cache + hot store

Command Mean [ms] Min [ms] Max [ms] Relative
pacquet@HEAD 668.0 ± 16.0 643.8 683.5 1.00
pacquet@main 689.9 ± 62.8 658.7 864.9 1.03 ± 0.10
pnpr@HEAD 801.5 ± 63.1 749.3 963.2 1.20 ± 0.10
pnpr@main 784.0 ± 12.2 770.6 804.8 1.17 ± 0.03
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 0.66799756696,
      "stddev": 0.016044897932155057,
      "median": 0.67595531356,
      "user": 0.38266290000000003,
      "system": 1.33117272,
      "min": 0.6437733040600001,
      "max": 0.6835120640600001,
      "times": [
        0.6817976410600001,
        0.6748963780600001,
        0.6789637960600001,
        0.6598654270600001,
        0.6835120640600001,
        0.6437733040600001,
        0.65108027306,
        0.6828877280600001,
        0.6770142490600001,
        0.6461848090600001
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 0.68988544296,
      "stddev": 0.06281213447515538,
      "median": 0.6635806240600001,
      "user": 0.39628450000000004,
      "system": 1.34035852,
      "min": 0.65871891406,
      "max": 0.8649313720600001,
      "times": [
        0.6614231450600001,
        0.66380677606,
        0.66284222006,
        0.6875972060600001,
        0.6613909570600001,
        0.8649313720600001,
        0.6790029850600001,
        0.6957863820600001,
        0.6633544720600001,
        0.65871891406
      ]
    },
    {
      "command": "pnpr@HEAD",
      "mean": 0.8014552614600001,
      "stddev": 0.06308520591484462,
      "median": 0.7859624655600002,
      "user": 0.4056162,
      "system": 1.3467674200000002,
      "min": 0.7492533990600001,
      "max": 0.9631821790600001,
      "times": [
        0.8062557570600001,
        0.7897613720600001,
        0.7504820220600001,
        0.7492533990600001,
        0.78090016506,
        0.9631821790600001,
        0.84078266306,
        0.7921656350600002,
        0.7821635590600001,
        0.7596058630600001
      ]
    },
    {
      "command": "pnpr@main",
      "mean": 0.78397074766,
      "stddev": 0.012181992706302344,
      "median": 0.7772556720600001,
      "user": 0.41656630000000006,
      "system": 1.34163392,
      "min": 0.7705875780600001,
      "max": 0.8047884880600001,
      "times": [
        0.8047884880600001,
        0.77536322706,
        0.7705875780600001,
        0.79347805806,
        0.8016168310600001,
        0.7763595080600001,
        0.7772337540600001,
        0.78809458206,
        0.7749078600600001,
        0.7772775900600001
      ]
    }
  ]
}

Scenario: Isolated linker: fresh install, cold cache + cold store

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 9.332 ± 0.037 9.286 9.409 1.84 ± 0.02
pacquet@main 9.291 ± 0.052 9.229 9.405 1.83 ± 0.02
pnpr@HEAD 5.083 ± 0.045 5.021 5.155 1.00
pnpr@main 5.122 ± 0.069 5.038 5.243 1.01 ± 0.02
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 9.331798343420001,
      "stddev": 0.03660474738625822,
      "median": 9.327309165920001,
      "user": 3.8006289800000004,
      "system": 3.3224377599999997,
      "min": 9.28626545042,
      "max": 9.409005505420001,
      "times": [
        9.318366496420001,
        9.29156254842,
        9.28626545042,
        9.34103869442,
        9.36785957442,
        9.33625183542,
        9.31573852942,
        9.409005505420001,
        9.34165773242,
        9.310237067420001
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 9.29067998852,
      "stddev": 0.052265325167259576,
      "median": 9.276342360420001,
      "user": 3.72971308,
      "system": 3.3457407599999995,
      "min": 9.228822044420001,
      "max": 9.40500780242,
      "times": [
        9.339530658420001,
        9.317843512420001,
        9.241413210420001,
        9.268362423420001,
        9.284322297420001,
        9.29594288742,
        9.26023035042,
        9.40500780242,
        9.228822044420001,
        9.26532469842
      ]
    },
    {
      "command": "pnpr@HEAD",
      "mean": 5.0830578083199995,
      "stddev": 0.04543835609711767,
      "median": 5.08954784542,
      "user": 2.4225156799999996,
      "system": 2.8377803599999996,
      "min": 5.020522532419999,
      "max": 5.15454772942,
      "times": [
        5.12231896542,
        5.020522532419999,
        5.07874348842,
        5.15454772942,
        5.0307805844199995,
        5.05720590042,
        5.12300506342,
        5.03662699242,
        5.10647462442,
        5.10035220242
      ]
    },
    {
      "command": "pnpr@main",
      "mean": 5.121674553419999,
      "stddev": 0.06885521272892708,
      "median": 5.10682162242,
      "user": 2.4213716799999996,
      "system": 2.84178076,
      "min": 5.03823921142,
      "max": 5.24270494842,
      "times": [
        5.22105608442,
        5.10733377742,
        5.06565665642,
        5.0978401374199995,
        5.16873765242,
        5.11642091142,
        5.03823921142,
        5.10630946742,
        5.05244668742,
        5.24270494842
      ]
    }
  ]
}

Scenario: Isolated linker: fresh install, hot cache + hot store

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 1.514 ± 0.048 1.458 1.619 2.26 ± 0.08
pacquet@main 1.493 ± 0.046 1.459 1.614 2.23 ± 0.08
pnpr@HEAD 0.670 ± 0.012 0.653 0.693 1.00
pnpr@main 0.719 ± 0.042 0.685 0.800 1.07 ± 0.07
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 1.51436899068,
      "stddev": 0.047602886114390265,
      "median": 1.51774994808,
      "user": 1.6245906599999997,
      "system": 1.8627569999999998,
      "min": 1.45786651908,
      "max": 1.61857636208,
      "times": [
        1.45786651908,
        1.47260214208,
        1.55131088408,
        1.61857636208,
        1.52332559308,
        1.46353584408,
        1.49538510308,
        1.51226822408,
        1.52323167208,
        1.52558756308
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 1.4926442256799999,
      "stddev": 0.04573369300188756,
      "median": 1.4808710285800002,
      "user": 1.63204116,
      "system": 1.8801020000000002,
      "min": 1.45889250408,
      "max": 1.61430209808,
      "times": [
        1.61430209808,
        1.5142985310800001,
        1.48344940308,
        1.47125389408,
        1.46746343808,
        1.45889250408,
        1.49233103608,
        1.46151187508,
        1.47829265408,
        1.48464682308
      ]
    },
    {
      "command": "pnpr@HEAD",
      "mean": 0.67035424418,
      "stddev": 0.012411180258385062,
      "median": 0.66797772408,
      "user": 0.33479256,
      "system": 1.2728153,
      "min": 0.65284358708,
      "max": 0.69302688808,
      "times": [
        0.69302688808,
        0.65284358708,
        0.6664994900800001,
        0.6799292800800001,
        0.67635679908,
        0.66300743108,
        0.66938457608,
        0.6665708720800001,
        0.6548771160800001,
        0.68104640208
      ]
    },
    {
      "command": "pnpr@main",
      "mean": 0.7191640084800001,
      "stddev": 0.04190614941979869,
      "median": 0.6967990260800001,
      "user": 0.36495866,
      "system": 1.296557,
      "min": 0.68523895308,
      "max": 0.8000534720800001,
      "times": [
        0.7604865420800001,
        0.69864653608,
        0.8000534720800001,
        0.77221194908,
        0.6876933630800001,
        0.6899641750800001,
        0.69495151608,
        0.6943560090800001,
        0.70803756908,
        0.68523895308
      ]
    }
  ]
}

Scenario: Isolated linker: fresh install, cold cache + hot store

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 5.083 ± 0.043 5.022 5.144 7.44 ± 0.19
pacquet@main 5.040 ± 0.048 4.993 5.157 7.37 ± 0.19
pnpr@HEAD 0.684 ± 0.017 0.667 0.715 1.00
pnpr@main 0.707 ± 0.055 0.673 0.853 1.03 ± 0.08
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 5.0834757676999995,
      "stddev": 0.043223050025307665,
      "median": 5.073019542199999,
      "user": 1.7836535599999999,
      "system": 1.9926053400000001,
      "min": 5.021619074699999,
      "max": 5.1444084747,
      "times": [
        5.021619074699999,
        5.1444084747,
        5.1183940387,
        5.0643242577,
        5.142850409699999,
        5.0637281017,
        5.028026050699999,
        5.073742203699999,
        5.0722968807,
        5.1053681847
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 5.0402602455,
      "stddev": 0.04818832815484789,
      "median": 5.0260545332,
      "user": 1.7819669599999997,
      "system": 1.97926194,
      "min": 4.9926543317,
      "max": 5.1573902207,
      "times": [
        5.0403019567,
        5.019054918699999,
        5.0166711427,
        5.000783160699999,
        5.0826651537,
        5.1573902207,
        5.0330541477,
        4.9926543317,
        5.0180003857,
        5.0420270366999995
      ]
    },
    {
      "command": "pnpr@HEAD",
      "mean": 0.6835624411000001,
      "stddev": 0.016850416718823875,
      "median": 0.6766562597000001,
      "user": 0.33916756,
      "system": 1.27173094,
      "min": 0.6673123487,
      "max": 0.7151254237,
      "times": [
        0.7151254237,
        0.6914615997,
        0.6721879977,
        0.6716680107,
        0.7077957547,
        0.6776290997000001,
        0.6889549897,
        0.6756834197,
        0.6673123487,
        0.6678057667
      ]
    },
    {
      "command": "pnpr@main",
      "mean": 0.7067162583000001,
      "stddev": 0.054709913881469,
      "median": 0.6812867967,
      "user": 0.34993415999999994,
      "system": 1.29395444,
      "min": 0.6726911837,
      "max": 0.8527297147,
      "times": [
        0.7213157907000001,
        0.6815405057,
        0.6794690707000001,
        0.8527297147,
        0.7278668097000001,
        0.6931235637000001,
        0.6810330877,
        0.6726911837,
        0.6808883067,
        0.6765045497000001
      ]
    }
  ]
}

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

🐰 Bencher Report

Branchpr/12273
Testbedpacquet

🚨 2 Alerts

BenchmarkMeasure
Units
ViewBenchmark Result
(Result Δ%)
Upper Boundary
(Limit %)
isolated-linker.fresh-install.cold-cache.cold-storeLatency
seconds (s)
📈 plot
🚷 threshold
🚨 alert (🔔)
9.33 s
(+69.64%)Baseline: 5.50 s
6.60 s
(141.36%)

isolated-linker.fresh-restore.cold-cache.cold-storeLatency
seconds (s)
📈 plot
🚷 threshold
🚨 alert (🔔)
9.96 s
(+25.63%)Baseline: 7.93 s
9.52 s
(104.69%)

Click to view all benchmark results
BenchmarkLatencyBenchmark Result
milliseconds (ms)
(Result Δ%)
Upper Boundary
milliseconds (ms)
(Limit %)
isolated-linker.fresh-install.cold-cache.cold-store📈 view plot
🚷 view threshold
🚨 view alert (🔔)
9,331.80 ms
(+69.64%)Baseline: 5,501.04 ms
6,601.25 ms
(141.36%)

isolated-linker.fresh-install.cold-cache.hot-store📈 view plot
🚷 view threshold
5,083.48 ms
(+1.67%)Baseline: 4,999.78 ms
5,999.73 ms
(84.73%)
isolated-linker.fresh-install.hot-cache.hot-store📈 view plot
🚷 view threshold
1,514.37 ms
(+10.10%)Baseline: 1,375.42 ms
1,650.50 ms
(91.75%)
isolated-linker.fresh-restore.cold-cache.cold-store📈 view plot
🚷 view threshold
🚨 view alert (🔔)
9,964.98 ms
(+25.63%)Baseline: 7,931.95 ms
9,518.34 ms
(104.69%)

isolated-linker.fresh-restore.hot-cache.hot-store📈 view plot
🚷 view threshold
668.00 ms
(+2.66%)Baseline: 650.68 ms
780.81 ms
(85.55%)
🐰 View full continuous benchmarking report in Bencher

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.

pacquet: peer-resolution node-sharing / cycle shadowing mismatch (blocks @babel/core suffix containment)

2 participants