feat(pacquet/cli): --filter, --filter-prod (initial)#12000
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 ignored due to path filters (1)
📒 Files selected for processing (21)
✅ Files skipped from review due to trivial changes (3)
🚧 Files skipped from review as they are similar to previous changes (16)
📜 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). (6)
🧰 Additional context used📓 Path-based instructions (1)pacquet/crates/**/*.rs📄 CodeRabbit inference engine (pacquet/AGENTS.md)
Files:
🧠 Learnings (3)📚 Learning: 2026-05-20T19:40:55.051ZApplied to files:
📚 Learning: 2026-05-22T00:08:44.646ZApplied to files:
📚 Learning: 2026-05-20T23:07:58.444ZApplied to files:
🔇 Additional comments (4)
📝 WalkthroughWalkthroughAdds global ChangesWorkspace Project Filtering Feature
Sequence Diagram(s)sequenceDiagram
participant CLI as CLI Parser
participant CliArgs
participant Config
participant FilterProj as filter_projects
participant Parser as parse_project_selector
participant GraphCtor as create_projects_graph
participant FilterEngine as filter_workspace_projects
CLI->>CliArgs: parse --filter / --filter-prod
CliArgs->>Config: copy filter vectors into Config
Config->>FilterProj: pass filters
FilterProj->>Parser: parse raw selector strings
FilterProj->>GraphCtor: build ProjectGraph(s)
FilterProj->>FilterEngine: apply parsed selectors to graph
FilterEngine->>FilterEngine: resolve entry points, traverse deps/dependents
FilterEngine->>FilterProj: return selected projects + unmatched
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes 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 docstrings
🧪 Generate unit tests (beta)
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. Comment |
Micro-Benchmark ResultsLinux |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #12000 +/- ##
==========================================
+ Coverage 89.20% 89.38% +0.17%
==========================================
Files 238 243 +5
Lines 32003 32536 +533
==========================================
+ Hits 28549 29081 +532
- Misses 3454 3455 +1 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Integrated-Benchmark Report (Linux)Scenario: Isolated linker: fresh restore, cold cache + cold store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 2.1083576487999998,
"stddev": 0.06279294754721261,
"median": 2.0885676146999996,
"user": 2.69096814,
"system": 3.28864934,
"min": 2.0510612566999997,
"max": 2.2523143857,
"times": [
2.1056892997,
2.1160715287,
2.1039139827,
2.1795793967,
2.0691338767,
2.0510612566999997,
2.2523143857,
2.0640837857,
2.0732212466999997,
2.0685077286999998
]
},
{
"command": "pacquet@main",
"mean": 2.0558177831999997,
"stddev": 0.045117370820884,
"median": 2.0481068077,
"user": 2.75835704,
"system": 3.2951159399999996,
"min": 2.0027051647,
"max": 2.1320898777,
"times": [
2.0027051647,
2.0617345067,
2.0128604337,
2.0924476007,
2.0559846947,
2.0137764427,
2.1320898777,
2.0293193126999998,
2.0402289206999997,
2.1170308777
]
}
]
}Scenario: Isolated linker: fresh restore, hot cache + hot store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 0.6819614637,
"stddev": 0.05537632821572339,
"median": 0.6634553345,
"user": 0.36889879999999997,
"system": 1.34143434,
"min": 0.645791674,
"max": 0.8326578820000001,
"times": [
0.8326578820000001,
0.7027885690000001,
0.675922019,
0.6734715290000001,
0.645791674,
0.6614897160000001,
0.6535233570000001,
0.655741942,
0.652806996,
0.665420953
]
},
{
"command": "pacquet@main",
"mean": 0.6810759167,
"stddev": 0.01579819562584968,
"median": 0.677368535,
"user": 0.3867216,
"system": 1.3588216399999997,
"min": 0.662231639,
"max": 0.7088218100000001,
"times": [
0.7088218100000001,
0.682150397,
0.6725866730000001,
0.6643783900000001,
0.6946824340000001,
0.68548622,
0.6702981870000001,
0.6706264100000001,
0.662231639,
0.6994970070000001
]
}
]
}Scenario: Isolated linker: fresh install, cold cache + cold store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 2.4381137769,
"stddev": 0.01834309003458254,
"median": 2.4422587578,
"user": 3.9658804400000003,
"system": 3.1250128999999998,
"min": 2.4016433392999996,
"max": 2.4573872393,
"times": [
2.4274090462999998,
2.4016433392999996,
2.4573872393,
2.4463186153,
2.4381989002999997,
2.4568592213,
2.4302209243,
2.4517748213,
2.4519787173,
2.4193469443
]
},
{
"command": "pacquet@main",
"mean": 2.4698891925999993,
"stddev": 0.018483365955591857,
"median": 2.4739184852999996,
"user": 4.042252840000001,
"system": 3.1252991999999997,
"min": 2.4414476442999997,
"max": 2.4904683113,
"times": [
2.4707651622999998,
2.4770718083,
2.4473728343,
2.4849243833,
2.4828737042999998,
2.4662512983,
2.4897501823,
2.4414476442999997,
2.4479665972999998,
2.4904683113
]
}
]
}Scenario: Isolated linker: fresh install, hot cache + hot store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 1.6885599412399999,
"stddev": 0.019978289402574765,
"median": 1.68921864994,
"user": 1.8765917399999996,
"system": 1.89500986,
"min": 1.66017798744,
"max": 1.7196604714400001,
"times": [
1.66017798744,
1.68608479344,
1.67668377744,
1.69481988244,
1.70162223344,
1.7152923164399998,
1.67072847444,
1.66817696944,
1.7196604714400001,
1.6923525064399998
]
},
{
"command": "pacquet@main",
"mean": 1.69283327474,
"stddev": 0.03259580752576191,
"median": 1.68900997944,
"user": 1.9035820400000003,
"system": 1.9264911599999999,
"min": 1.63660581244,
"max": 1.76551919644,
"times": [
1.68773787344,
1.6880788514399998,
1.76551919644,
1.69728121844,
1.71005452744,
1.63660581244,
1.66653921744,
1.68895629044,
1.69849609144,
1.68906366844
]
}
]
} |
|
| Branch | pr/12000 |
| Testbed | pacquet |
Click to view all benchmark results
| Benchmark | Latency | Benchmark Result milliseconds (ms) (Result Δ%) | Upper Boundary milliseconds (ms) (Limit %) |
|---|---|---|---|
| isolated-linker.fresh-install.cold-cache.cold-store | 📈 view plot 🚷 view threshold | 2,438.11 ms(+2.32%)Baseline: 2,382.88 ms | 2,859.45 ms (85.26%) |
| isolated-linker.fresh-install.hot-cache.hot-store | 📈 view plot 🚷 view threshold | 1,688.56 ms(+12.86%)Baseline: 1,496.14 ms | 1,795.37 ms (94.05%) |
| isolated-linker.fresh-restore.cold-cache.cold-store | 📈 view plot 🚷 view threshold | 2,108.36 ms(-0.83%)Baseline: 2,126.10 ms | 2,551.32 ms (82.64%) |
| isolated-linker.fresh-restore.hot-cache.hot-store | 📈 view plot 🚷 view threshold | 681.96 ms(-1.58%)Baseline: 692.88 ms | 831.45 ms (82.02%) |
There was a problem hiding this comment.
Pull request overview
Adds initial Rust ports of pnpm’s workspace project graph + filtering logic and wires new CLI flags into pacquet config, so selectors can be parsed/stored (selection is implemented in the new crates but not yet consumed by install).
Changes:
- Introduces
pacquet-workspace-projects-graph(dependency edge graph between workspace projects). - Introduces
pacquet-workspace-projects-filter(selector parsing + project selection, with[since]diff selectors rejected as unsupported for now). - Adds CLI flags
--filter/-Fand--filter-prod, threading them intopacquet_config::Config.
Reviewed changes
Copilot reviewed 21 out of 22 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| pacquet/plans/TEST_PORTING.md | Documents port status for workspace filtering/graph logic and known-failure stubs. |
| pacquet/crates/workspace-projects-graph/Cargo.toml | New crate manifest for the Rust projects graph port. |
| pacquet/crates/workspace-projects-graph/src/lib.rs | Public API + module wiring for the graph crate. |
| pacquet/crates/workspace-projects-graph/src/base_project.rs | Defines BaseProject / GraphProject traits used by graph construction. |
| pacquet/crates/workspace-projects-graph/src/graph.rs | Defines ProjectGraph / ProjectGraphNode types and ordering contract. |
| pacquet/crates/workspace-projects-graph/src/create_projects_graph.rs | Implements create_projects_graph and edge resolution logic. |
| pacquet/crates/workspace-projects-graph/src/create_projects_graph/tests.rs | Unit tests covering workspace spec/range/path edge resolution behavior. |
| pacquet/crates/workspace-projects-filter/Cargo.toml | New crate manifest for the Rust projects filter port. |
| pacquet/crates/workspace-projects-filter/src/lib.rs | Public API for selector parsing and filtering entry points. |
| pacquet/crates/workspace-projects-filter/src/path_util.rs | Adds lexical_join helper for directory selectors. |
| pacquet/crates/workspace-projects-filter/src/parse_project_selector.rs | Implements selector parsing (name/dir/…/^/!/diff). |
| pacquet/crates/workspace-projects-filter/src/parse_project_selector/tests.rs | Ports upstream selector parsing fixtures. |
| pacquet/crates/workspace-projects-filter/src/filter.rs | Implements workspace selection (include/exclude, deps/dependents walking). |
| pacquet/crates/workspace-projects-filter/src/filter/tests.rs | Ports upstream filtering fixtures (+ known failures for [since]). |
| pacquet/crates/workspace-projects-filter/src/glob.rs | Implements minimal */** directory glob matcher for glob dir filtering. |
| pacquet/crates/workspace-projects-filter/src/glob/tests.rs | Tests glob behavior used by directory filtering. |
| pacquet/crates/config/src/lib.rs | Adds CLI-only config fields filter and filter_prod. |
| pacquet/crates/cli/src/cli_args.rs | Adds --filter/-F and --filter-prod flags and threads them into Config. |
| pacquet/crates/cli/src/cli_args/tests.rs | Adds parsing tests for new filter flags. |
| pacquet/crates/package-manager/src/install_package_from_registry/tests.rs | Updates config test helper to include new fields. |
| Cargo.toml | Adds new crates to workspace dependencies. |
| Cargo.lock | Records new workspace crates and deps. |
Comments suppressed due to low confidence (8)
pacquet/crates/workspace-projects-filter/src/glob.rs:21
- In
is_match, only the pattern is normalized to forward slashes. On Windows,PathBuf::to_string_lossy()typically yields\separators for project root dirs, so glob dir filtering will fail to match even when the logical paths line up. Normalize the candidate string too (and keep the normalization buffer alive while splitting).
This issue also appears on line 13 of the same file.
pub fn is_match(candidate: &str, pattern: &str) -> bool {
let pattern = pattern.replace('\\', "/");
let pattern = pattern.strip_suffix('/').unwrap_or(&pattern);
let candidate = candidate.strip_suffix('/').unwrap_or(candidate);
let pattern_segments: Vec<&str> = pattern.split('/').collect();
let candidate_segments: Vec<&str> = candidate.split('/').collect();
match_segments(&pattern_segments, &candidate_segments)
}
pacquet/crates/workspace-projects-filter/src/parse_project_selector.rs:36
- Doc comment has a duplicated word: "directory directory-selectors".
This issue also appears on line 35 of the same file.
/// Parse one raw `--filter` selector string against `prefix` (the
/// directory directory-selectors resolve relative to).
pacquet/crates/workspace-projects-graph/src/create_projects_graph.rs:204
resolve_directoryonly does an exact lexical-normalized lookup. UpstreamcreateProjectsGraphhas an additional slow-path scan to handle case mismatches on case-insensitive filesystems (solink:../Bcan still match a workspace project rooted at../b). Without a similar fallback, inter-project edges may be missed on Windows/macOS when dependency path casing differs from the actual directory casing.
This issue also appears on line 198 of the same file.
/// Resolve a local-path dependency to a sibling by directory: join the
/// path onto the importer's root, normalize, and look it up in the
/// by-directory index.
fn resolve_directory(importer: usize, path: &str, lookups: &Lookups) -> Option<PathBuf> {
let resolved = lexical_normalize(&lookups.node_keys[importer].join(path));
lookups.by_dir.get(&resolved).map(|&index| lookups.node_keys[index].clone())
}
pacquet/crates/workspace-projects-filter/src/filter.rs:143
filter_graphclones adjacency lists on every traversal (node.dependencies.clone()andgraph.get(id).cloned()), which can become a noticeable overhead for large workspaces because each visited node allocates and copies its edge list. Consider changingpick_subgraph/adjto borrow adjacency slices (e.g.,Fn(&Path) -> Option<&[PathBuf]>) so traversal can walk without cloning.
This issue also appears on line 137 of the same file.
let forward = |id: &Path| projects_graph.get(id).map(|node| node.dependencies.clone());
let reversed_graph = selectors
.iter()
.any(|selector| selector.include_dependents)
.then(|| reverse_graph(projects_graph));
let reverse = |id: &Path| reversed_graph.as_ref().and_then(|graph| graph.get(id).cloned());
pacquet/crates/workspace-projects-filter/src/glob.rs:21
- In
is_match, only the pattern is normalized to forward slashes. On Windows,PathBuf::to_string_lossy()typically yields\separators for project root dirs, so glob dir filtering will fail to match even when the logical paths line up. Normalize the candidate string too (and keep the normalization buffer alive while splitting).
This issue also appears on line 13 of the same file.
pub fn is_match(candidate: &str, pattern: &str) -> bool {
let pattern = pattern.replace('\\', "/");
let pattern = pattern.strip_suffix('/').unwrap_or(&pattern);
let candidate = candidate.strip_suffix('/').unwrap_or(candidate);
let pattern_segments: Vec<&str> = pattern.split('/').collect();
let candidate_segments: Vec<&str> = candidate.split('/').collect();
match_segments(&pattern_segments, &candidate_segments)
}
pacquet/crates/workspace-projects-filter/src/parse_project_selector.rs:36
- Doc comment has a duplicated word: "directory directory-selectors".
This issue also appears on line 35 of the same file.
/// Parse one raw `--filter` selector string against `prefix` (the
/// directory directory-selectors resolve relative to).
pacquet/crates/workspace-projects-graph/src/create_projects_graph.rs:204
resolve_directoryonly does an exact lexical-normalized lookup. UpstreamcreateProjectsGraphhas an additional slow-path scan to handle case mismatches on case-insensitive filesystems (solink:../Bcan still match a workspace project rooted at../b). Without a similar fallback, inter-project edges may be missed on Windows/macOS when dependency path casing differs from the actual directory casing.
This issue also appears on line 198 of the same file.
/// Resolve a local-path dependency to a sibling by directory: join the
/// path onto the importer's root, normalize, and look it up in the
/// by-directory index.
fn resolve_directory(importer: usize, path: &str, lookups: &Lookups) -> Option<PathBuf> {
let resolved = lexical_normalize(&lookups.node_keys[importer].join(path));
lookups.by_dir.get(&resolved).map(|&index| lookups.node_keys[index].clone())
}
pacquet/crates/workspace-projects-filter/src/filter.rs:143
filter_graphclones adjacency lists on every traversal (node.dependencies.clone()andgraph.get(id).cloned()), which can become a noticeable overhead for large workspaces because each visited node allocates and copies its edge list. Consider changingpick_subgraph/adjto borrow adjacency slices (e.g.,Fn(&Path) -> Option<&[PathBuf]>) so traversal can walk without cloning.
This issue also appears on line 137 of the same file.
let forward = |id: &Path| projects_graph.get(id).map(|node| node.dependencies.clone());
let reversed_graph = selectors
.iter()
.any(|selector| selector.include_dependents)
.then(|| reverse_graph(projects_graph));
let reverse = |id: &Path| reversed_graph.as_ref().and_then(|graph| graph.get(id).cloned());
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (3)
pacquet/plans/TEST_PORTING.md (2)
266-266: ⚡ Quick winClarify the directional reference to hoist stubs.
The phrase "the two
known_failureshoist stubs below stay" is confusing because the referenced hoist stubs are in the "Hoisting" section (lines 123-124), which appears above this new section in the file, not below.📝 Suggested clarification
-selected projects is still a follow-up (the install fan-out is -unfiltered, so the two `known_failures` hoist stubs below stay). +selected projects is still a follow-up (the install fan-out is +unfiltered, so the two `known_failures` hoist stubs in the "Hoisting" +section remain blocked).🤖 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 `@pacquet/plans/TEST_PORTING.md` at line 266, The sentence referencing "the two `known_failures` hoist stubs below stay" is misleading because the Hoisting section (which contains those `known_failures` stubs) is above this paragraph; update the sentence in TEST_PORTING.md to explicitly reference the Hoisting section or change "below" to "above" (e.g., "the two `known_failures` hoist stubs in the 'Hoisting' section remain") so readers can locate the `known_failures` hoist stubs unambiguously.
300-302: 💤 Low valueMake stubbed test documentation consistent.
The first stubbed test includes the test name in parentheses (
git_diff_selection_unimplemented), but the next two only say "Stubbed." For consistency and discoverability, either include the stub name for all three or omit it for all three.📝 Suggested fix for consistency
If the stub names differ, include them:
-- [x] `TypeScript repo: workspace/projects-filter/test/index.ts:348` `select changed packages`. Stubbed (`git_diff_selection_unimplemented`). -- [x] `TypeScript repo: workspace/projects-filter/test/index.ts:480` `select changed packages when operating under a git worktree`. Stubbed. -- [x] `TypeScript repo: workspace/projects-filter/test/index.ts:553` `selection should fail when diffing to a branch that does not exist`. Stubbed. ++ [x] `TypeScript repo: workspace/projects-filter/test/index.ts:348` `select changed packages`. Stubbed (`git_diff_selection_unimplemented`). ++ [x] `TypeScript repo: workspace/projects-filter/test/index.ts:480` `select changed packages when operating under a git worktree`. Stubbed (`git_diff_selection_unimplemented`). ++ [x] `TypeScript repo: workspace/projects-filter/test/index.ts:553` `selection should fail when diffing to a branch that does not exist`. Stubbed (`git_diff_selection_unimplemented`).Or, if they all use the same stub and the rejection path is already documented on line 296, omit the parenthetical entirely:
-- [x] `TypeScript repo: workspace/projects-filter/test/index.ts:348` `select changed packages`. Stubbed (`git_diff_selection_unimplemented`). ++ [x] `TypeScript repo: workspace/projects-filter/test/index.ts:348` `select changed packages`. Stubbed.🤖 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 `@pacquet/plans/TEST_PORTING.md` around lines 300 - 302, The three checklist items referencing tests "select changed packages" (workspace/projects-filter/test/index.ts:348), "select changed packages when operating under a git worktree" (index.ts:480), and "selection should fail when diffing to a branch that does not exist" (index.ts:553) are inconsistent: the first shows the stub name (git_diff_selection_unimplemented) in parentheses while the other two just say "Stubbed." Make them consistent by either adding the stub name "(git_diff_selection_unimplemented)" to the two latter entries or removing the parenthetical from the first entry so all three simply read "Stubbed."; update the three checklist lines accordingly and ensure the exact stub identifier git_diff_selection_unimplemented is used if you choose to include names.pacquet/crates/cli/src/cli_args.rs (1)
150-151: 💤 Low valueConsider moving instead of cloning.
filterandfilter_prodare owned values (moved out at line 112) and are only used once here. Direct assignment would avoid the clone:- cfg.filter.clone_from(&filter); - cfg.filter_prod.clone_from(&filter_prod); + cfg.filter = filter; + cfg.filter_prod = filter_prod;🤖 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 `@pacquet/crates/cli/src/cli_args.rs` around lines 150 - 151, The two lines use clone_from on owned values; replace these clones by moving the owned values into the config fields instead of cloning—i.e., stop calling cfg.filter.clone_from(&filter) and cfg.filter_prod.clone_from(&filter_prod) and assign the owned filter and filter_prod into cfg.filter and cfg.filter_prod respectively so the ownership is moved rather than cloned.
🤖 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 `@pacquet/crates/workspace-projects-filter/src/path_util.rs`:
- Around line 4-12: lexical_join currently uses Path::join which replaces the
prefix when rel is absolute; to match Node/pnpm path.join semantics (where an
absolute rel is concatenated), change lexical_join to detect when rel is an
absolute-looking string and strip leading root separators (e.g. '/' and '\\')
before joining so prefix.join(<stripped rel>) is used, then pass the result to
lexical_normalize; update logic in the lexical_join function (and any helper
that parses rel) to trim leading separators rather than relying on Path::join to
retain prefix.
---
Nitpick comments:
In `@pacquet/crates/cli/src/cli_args.rs`:
- Around line 150-151: The two lines use clone_from on owned values; replace
these clones by moving the owned values into the config fields instead of
cloning—i.e., stop calling cfg.filter.clone_from(&filter) and
cfg.filter_prod.clone_from(&filter_prod) and assign the owned filter and
filter_prod into cfg.filter and cfg.filter_prod respectively so the ownership is
moved rather than cloned.
In `@pacquet/plans/TEST_PORTING.md`:
- Line 266: The sentence referencing "the two `known_failures` hoist stubs below
stay" is misleading because the Hoisting section (which contains those
`known_failures` stubs) is above this paragraph; update the sentence in
TEST_PORTING.md to explicitly reference the Hoisting section or change "below"
to "above" (e.g., "the two `known_failures` hoist stubs in the 'Hoisting'
section remain") so readers can locate the `known_failures` hoist stubs
unambiguously.
- Around line 300-302: The three checklist items referencing tests "select
changed packages" (workspace/projects-filter/test/index.ts:348), "select changed
packages when operating under a git worktree" (index.ts:480), and "selection
should fail when diffing to a branch that does not exist" (index.ts:553) are
inconsistent: the first shows the stub name (git_diff_selection_unimplemented)
in parentheses while the other two just say "Stubbed." Make them consistent by
either adding the stub name "(git_diff_selection_unimplemented)" to the two
latter entries or removing the parenthetical from the first entry so all three
simply read "Stubbed."; update the three checklist lines accordingly and ensure
the exact stub identifier git_diff_selection_unimplemented is used if you choose
to include names.
🪄 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: 413a711d-7a28-4f80-ba71-bff28fc21d85
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (21)
Cargo.tomlpacquet/crates/cli/src/cli_args.rspacquet/crates/cli/src/cli_args/tests.rspacquet/crates/config/src/lib.rspacquet/crates/package-manager/src/install_package_from_registry/tests.rspacquet/crates/workspace-projects-filter/Cargo.tomlpacquet/crates/workspace-projects-filter/src/filter.rspacquet/crates/workspace-projects-filter/src/filter/tests.rspacquet/crates/workspace-projects-filter/src/glob.rspacquet/crates/workspace-projects-filter/src/glob/tests.rspacquet/crates/workspace-projects-filter/src/lib.rspacquet/crates/workspace-projects-filter/src/parse_project_selector.rspacquet/crates/workspace-projects-filter/src/parse_project_selector/tests.rspacquet/crates/workspace-projects-filter/src/path_util.rspacquet/crates/workspace-projects-graph/Cargo.tomlpacquet/crates/workspace-projects-graph/src/base_project.rspacquet/crates/workspace-projects-graph/src/create_projects_graph.rspacquet/crates/workspace-projects-graph/src/create_projects_graph/tests.rspacquet/crates/workspace-projects-graph/src/graph.rspacquet/crates/workspace-projects-graph/src/lib.rspacquet/plans/TEST_PORTING.md
📜 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: Lint and Test (ubuntu-latest)
- GitHub Check: Lint and Test (macos-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
- GitHub Check: Compile & Lint
🧰 Additional context used
📓 Path-based instructions (1)
pacquet/**/*.rs
📄 CodeRabbit inference engine (pacquet/AGENTS.md)
pacquet/**/*.rs: Warnings are errors (--deny warningsin lint). Do not silence them with#[allow(...)]unless there is a specific, justified reason.
Choose owned vs. borrowed parameters to minimize copies; widen to the most encompassing type (&Pathover&PathBuf,&strover&String) when it doesn't force extra copies.
PreferArc::clone(&x)/Rc::clone(&x)overx.clone()for reference-counted types, so the cost is visible at the call site.
Follow Rust API Guidelines for naming: https://rust-lang.github.io/api-guidelines/naming.html
No star imports inside module bodies. Writeuse super::{Foo, bar}instead ofuse super::*;, and the same for any other glob whose target is a module you control. Two forms stay allowed: external-crate preludes such asuse rayon::prelude::*;and root-of-module re-exports such aspub use submodule::*;in alib.rs.
Doc comments (///,//!) document the contract: preconditions, postconditions, panics, the reason the function exists. They are not a re-narration of the body.
Do not restate at call sites what the callee's doc comment already says. If///on the function says 'no-op when …', the caller should not repeat that. Update the doc once; let every call site benefit.
Tests are documentation. Do not duplicate them in prose. If a behavioral scenario, edge case, failure mode, or worked example is already captured by a test, do not also narrate it in the doc comment on the implementation. The same applies in reverse: a test's own doc comment should not re-explain what the asserts already say, only the why if it is not obvious.
Use// SAFETY:,// TODO:, and similar prefixes to signal hidden invariants or known follow-ups that a reader cannot recover from the code alone.
When editing existing code, do not break a method chain (includingpipe-trait.pipe(...)chains) into intermediateletbindings unless you can justify the rewrite. Valid justifications include a chain that fails to compile after your...
Files:
pacquet/crates/workspace-projects-filter/src/path_util.rspacquet/crates/cli/src/cli_args.rspacquet/crates/workspace-projects-graph/src/graph.rspacquet/crates/package-manager/src/install_package_from_registry/tests.rspacquet/crates/config/src/lib.rspacquet/crates/workspace-projects-graph/src/lib.rspacquet/crates/workspace-projects-graph/src/base_project.rspacquet/crates/workspace-projects-filter/src/lib.rspacquet/crates/cli/src/cli_args/tests.rspacquet/crates/workspace-projects-filter/src/parse_project_selector/tests.rspacquet/crates/workspace-projects-filter/src/parse_project_selector.rspacquet/crates/workspace-projects-graph/src/create_projects_graph.rspacquet/crates/workspace-projects-filter/src/glob/tests.rspacquet/crates/workspace-projects-filter/src/glob.rspacquet/crates/workspace-projects-filter/src/filter.rspacquet/crates/workspace-projects-filter/src/filter/tests.rspacquet/crates/workspace-projects-graph/src/create_projects_graph/tests.rs
🧠 Learnings (3)
📚 Learning: 2026-05-20T19:40:55.051Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11774
File: pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs:0-0
Timestamp: 2026-05-20T19:40:55.051Z
Learning: In the pacquet Rust code, ensure the semver implementation uses the `node-semver` crate (not `nodejs-semver`). `node-semver`’s public API does not include a `satisfies_with_prerelease`-style method; prerelease-tolerant matching should be implemented inline by first calling `Range::satisfies`, and when it rejects a prerelease version, retry matching against a stripped `MAJOR.MINOR.PATCH` base of the prerelease version.
Applied to files:
pacquet/crates/workspace-projects-filter/src/path_util.rspacquet/crates/cli/src/cli_args.rspacquet/crates/workspace-projects-graph/src/graph.rspacquet/crates/package-manager/src/install_package_from_registry/tests.rspacquet/crates/config/src/lib.rspacquet/crates/workspace-projects-graph/src/lib.rspacquet/crates/workspace-projects-graph/src/base_project.rspacquet/crates/workspace-projects-filter/src/lib.rspacquet/crates/cli/src/cli_args/tests.rspacquet/crates/workspace-projects-filter/src/parse_project_selector/tests.rspacquet/crates/workspace-projects-filter/src/parse_project_selector.rspacquet/crates/workspace-projects-graph/src/create_projects_graph.rspacquet/crates/workspace-projects-filter/src/glob/tests.rspacquet/crates/workspace-projects-filter/src/glob.rspacquet/crates/workspace-projects-filter/src/filter.rspacquet/crates/workspace-projects-filter/src/filter/tests.rspacquet/crates/workspace-projects-graph/src/create_projects_graph/tests.rs
📚 Learning: 2026-05-22T00:08:44.646Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11837
File: pacquet/crates/resolving-npm-resolver/src/pick_package.rs:33-51
Timestamp: 2026-05-22T00:08:44.646Z
Learning: In the pnpm/pnpm repo’s pacquet Rust crates, do not flag Unicode ellipsis characters (U+2026, `…`) in Rust doc comments (`///` / `/** */`) as a lint violation. The pacquet crate’s `dylint.toml` only enables `perfectionist::derive_ordering`, and the Dylint `unicode-ellipsis` rule is not enabled for this project—so `…` in doc comments is an intentional, repo-consistent style.
Applied to files:
pacquet/crates/workspace-projects-filter/src/path_util.rspacquet/crates/cli/src/cli_args.rspacquet/crates/workspace-projects-graph/src/graph.rspacquet/crates/package-manager/src/install_package_from_registry/tests.rspacquet/crates/config/src/lib.rspacquet/crates/workspace-projects-graph/src/lib.rspacquet/crates/workspace-projects-graph/src/base_project.rspacquet/crates/workspace-projects-filter/src/lib.rspacquet/crates/cli/src/cli_args/tests.rspacquet/crates/workspace-projects-filter/src/parse_project_selector/tests.rspacquet/crates/workspace-projects-filter/src/parse_project_selector.rspacquet/crates/workspace-projects-graph/src/create_projects_graph.rspacquet/crates/workspace-projects-filter/src/glob/tests.rspacquet/crates/workspace-projects-filter/src/glob.rspacquet/crates/workspace-projects-filter/src/filter.rspacquet/crates/workspace-projects-filter/src/filter/tests.rspacquet/crates/workspace-projects-graph/src/create_projects_graph/tests.rs
📚 Learning: 2026-05-20T23:07:58.444Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11784
File: pacquet/crates/resolving-deps-resolver/src/hoist_peers.rs:120-133
Timestamp: 2026-05-20T23:07:58.444Z
Learning: When reviewing code in this pacquet Rust port, follow the upstream pnpm compatibility rule: only match pnpm’s behavior exactly. Do not propose review changes that intentionally deviate from pnpm’s documented/observed behavior, even if pnpm appears buggy. If you identify a real bug in pnpm behavior, the review should prioritize fixing it upstream in pnpm first, and avoid implementing a pnpm-behavior workaround here unless the same fix has already landed upstream.
Applied to files:
pacquet/crates/workspace-projects-filter/src/path_util.rspacquet/crates/cli/src/cli_args.rspacquet/crates/workspace-projects-graph/src/graph.rspacquet/crates/package-manager/src/install_package_from_registry/tests.rspacquet/crates/config/src/lib.rspacquet/crates/workspace-projects-graph/src/lib.rspacquet/crates/workspace-projects-graph/src/base_project.rspacquet/crates/workspace-projects-filter/src/lib.rspacquet/crates/cli/src/cli_args/tests.rspacquet/crates/workspace-projects-filter/src/parse_project_selector/tests.rspacquet/crates/workspace-projects-filter/src/parse_project_selector.rspacquet/crates/workspace-projects-graph/src/create_projects_graph.rspacquet/crates/workspace-projects-filter/src/glob/tests.rspacquet/crates/workspace-projects-filter/src/glob.rspacquet/crates/workspace-projects-filter/src/filter.rspacquet/crates/workspace-projects-filter/src/filter/tests.rspacquet/crates/workspace-projects-graph/src/create_projects_graph/tests.rs
🔇 Additional comments (25)
pacquet/crates/workspace-projects-graph/Cargo.toml (1)
1-22: LGTM!pacquet/crates/workspace-projects-graph/src/base_project.rs (1)
1-31: LGTM!pacquet/crates/workspace-projects-graph/src/graph.rs (1)
1-25: LGTM!pacquet/crates/workspace-projects-graph/src/lib.rs (1)
17-26: LGTM!pacquet/crates/workspace-projects-filter/Cargo.toml (1)
1-26: LGTM!pacquet/crates/workspace-projects-graph/src/create_projects_graph.rs (2)
12-136: LGTM!
161-298: LGTM!pacquet/crates/workspace-projects-graph/src/create_projects_graph/tests.rs (1)
8-253: LGTM!Cargo.toml (1)
63-64: LGTM!pacquet/crates/cli/src/cli_args.rs (2)
47-67: LGTM!
112-112: LGTM!pacquet/crates/cli/src/cli_args/tests.rs (1)
27-64: LGTM!pacquet/crates/config/src/lib.rs (1)
819-832: LGTM!pacquet/crates/package-manager/src/install_package_from_registry/tests.rs (1)
73-74: LGTM!pacquet/crates/workspace-projects-filter/src/parse_project_selector.rs (2)
11-98: LGTM!
103-204: LGTM!pacquet/crates/workspace-projects-filter/src/parse_project_selector/tests.rs (1)
18-258: LGTM!pacquet/crates/workspace-projects-filter/src/glob.rs (1)
13-68: LGTM!pacquet/crates/workspace-projects-filter/src/glob/tests.rs (1)
3-48: LGTM!pacquet/crates/workspace-projects-filter/src/filter.rs (4)
86-114: LGTM!
123-239: LGTM!
244-305: LGTM!
312-395: LGTM!pacquet/crates/workspace-projects-filter/src/filter/tests.rs (1)
12-575: LGTM!pacquet/crates/workspace-projects-filter/src/lib.rs (1)
20-32: LGTM!
…ject filtering
Port pnpm's `@pnpm/workspace.projects-graph` and
`@pnpm/workspace.projects-filter` as the new
`pacquet-workspace-projects-graph` and `pacquet-workspace-projects-filter`
crates, and expose the `--filter` / `--filter-prod` CLI flags (stored into
`Config::filter` / `Config::filter_prod`).
- `parse_project_selector` parses selector strings (name glob, `...`
dependents/dependencies, `^` exclude-self, `!` exclude, `{dir}`, `[since]`).
- `create_projects_graph` computes inter-project edges via `workspace:`,
semver version/range, and local-path resolution.
- `filter_workspace_projects` resolves selectors against the graph;
`filter_projects` builds the graph(s) and applies a `WorkspaceFilter` list.
The `[since]` changed-packages selector parses but is rejected with
`FilterError::UnsupportedDiffSelector` (git-diff project selection is not
ported). As with `--recursive`, the install still materializes every
workspace importer in one shared pass, so narrowing the install to the
selected projects remains a follow-up; the `known_failures` hoist stubs for
`--filter` selected-projects installs are unchanged.
Ports the upstream `parseProjectSelector` and `filterWorkspaceProjects` tests;
the diff-based cases are stubbed as `known_failures`. Updates
plans/TEST_PORTING.md.
https://claude.ai/code/session_01BsrsKRRp5ntwqnTB7YREes
`parse_project_selector` is both a module and a re-exported function at the crate root, so the bare intra-doc link was ambiguous and failed `cargo doc` under `-D warnings`. Link to the function with `()`. https://claude.ai/code/session_01BsrsKRRp5ntwqnTB7YREes
…ph parity
Review-driven fixes for parity with upstream `@pnpm/workspace.projects-filter`
and `projects-graph`:
- `parse_project_selector`: the hand-rolled selector matcher now backtracks
like the upstream regex `^([^.][^{}[\]]*)?(\{[^}]+\})?(\[[^\]]+\])?$`. The
name group's first char may be a brace/bracket, so `!{foo` is an exclude
selector (was previously parsed as include, inverting selection) and
`{[master]` keeps its `[master]` diff. Added cases.
- `create_projects_graph`: a path-style `workspace:` token (`workspace:../foo`)
now resolves by directory rather than failing version resolution and
emitting a spurious `unmatched` entry, matching upstream's
`workspacePrefToNpm` + `npa.resolve` path.
- `glob::segment_match`: replaced the greedy single-pass match with a
backtracking wildcard match so multiple `*` in one path segment
(`{packages/a*-*}`) match correctly.
- Deduplicated `lexical_normalize`: it now lives in
`pacquet-workspace-projects-graph` and the filter crate reuses it.
https://claude.ai/code/session_01BsrsKRRp5ntwqnTB7YREes
…rustdoc CI - Rename closure params off single letters (`|p|` → `|project|`, `|&v|` → `|&version|`) to satisfy `perfectionist::single_letter_closure_param`. - Use raw strings for the `.\` / `..\` path prefixes (`perfectionist::prefer_raw_string`) and add the missing `matches!` trailing comma (`perfectionist::macro_trailing_comma`). - Disambiguate every `create_projects_graph` intra-doc link with `()` (module and re-exported function share the name), fixing `rustdoc::broken_intra_doc_links` under nightly. - Rename the `unparseable_braces…` test to `unparsable_…` (typos). https://claude.ai/code/session_01BsrsKRRp5ntwqnTB7YREes
…gs; add filter-prod coverage - Reorder derives to `prefix_then_alphabetical` (Default before Clone) for `perfectionist::derive_ordering`. - Replace U+2026 `…` with ASCII `...` in comments (`perfectionist::unicode_ellipsis_in_comments`). - Add coverage the cloud review flagged as missing: the `--filter-prod` production-only graph (dev edges dropped), the prod-then-all union order, and `unmatched_filters` for a path selector that matches nothing. - Document the clap global-flag limitation: `--filter` occurrences only merge within one side of the subcommand boundary. Verified locally with `cargo dylint --all` and `cargo doc -D warnings`. https://claude.ai/code/session_01BsrsKRRp5ntwqnTB7YREes
…verage
Add unit tests for every reachable line CodeCov flagged uncovered, and
delete the one unreachable branch:
- parser: empty input (`...`), empty brace group (`{}`), dot-prefixed
name (`.foo`).
- filter: selector combining a directory and a name pattern, a selector
with no name/dir/diff (`UnsupportedSelector`), and the `is_subdir`
contract (incomparable / descendant / equal paths).
- glob: trailing `*` after an exact prefix.
- graph: `./`-segment collapse in a path spec, and the Windows
drive-prefix predicate.
- Remove the unreachable `candidates.is_empty()` guard in
`resolve_by_name_version` (`by_name` never holds empty vecs; `?`
already covers absence).
All pure in-memory logic, so plain unit tests with constructed inputs
fit — no integration, filesystem fixtures, or DI seam needed. Verified
with `cargo dylint --all` and `cargo doc -D warnings`.
https://claude.ai/code/session_01BsrsKRRp5ntwqnTB7YREes
…normalize The graph crate carried its own copy of `lexical_normalize`, duplicating `pacquet-fs`'s already fully-tested helper. For every input the graph passes (absolute project roots and absolute-base joins) the two behave identically — they differ only on unanchored leading `..`, which never reaches this code. Drop the duplicate and re-export the `pacquet-fs` one, removing the lone uncovered `Component::CurDir` branch in the process (the shared helper already covers it). https://claude.ai/code/session_0144D5iK3jHxrWENJFq6m8KF
`pick_subgraph` skips a node it has already inserted via the
`walked.contains` guard, the branch that keeps the walk terminating on
diamonds and cycles. None of the existing fixtures reach a node by two
paths, so add a diamond graph (`top` -> {`left`, `right`} -> `shared`)
whose `...` dependency walk visits `shared` twice, exercising the guard.
https://claude.ai/code/session_0144D5iK3jHxrWENJFq6m8KF
Drop the second paragraph narrating the current install behavior
("still materializes every importer ... follow-up ... stored for
parity"). That described a transient state that goes stale the moment
pacquet consumes the filter during install. Keep only the stable
contract: what the field holds, that it mirrors pnpm's CLI-only
`filter` array, and that only the CLI layer populates it.
https://claude.ai/code/session_0144D5iK3jHxrWENJFq6m8KF
…y selectors like path.join
Upstream's `parseProjectSelector` resolves directory selectors with
`path.join(prefix, rel)`, which concatenates even when `rel` is absolute
(`path.join('/ws', '/pkg')` -> `/ws/pkg`). Rust's `Path::join` instead
drops `prefix` for an absolute `rel`, so `lexical_join` resolved a `{/pkg}`
selector to `/pkg` rather than `/ws/pkg`, diverging from pnpm.
Strip leading separators from `rel` before joining so an absolute
selector extends the prefix, and correct the doc comment, which described
the wrong (Rust) semantics. Add a parse test covering `{/pkg}`.
…rators in dir globs
Address PR review comments:
- glob: normalize backslashes to `/` in the candidate path as well as the
pattern, so a Windows `ProjectRootDir` rendered with backslashes by
`PathBuf::to_string_lossy()` still matches a glob dir selector. Matches
micromatch's effective separator handling.
- filter: include the offending selector in `UnsupportedSelector`,
mirroring upstream's `Unsupported project selector: ${JSON.stringify(selector)}`.
- cli: add a regression test locking the documented clap mixed-placement
limitation for the global `-F` flag.
- parse_project_selector: fix a doc typo ("directory directory-selectors").
d35b7a8 to
cefd4cf
Compare
…ral doc - Re-pad the [dependencies] table so every `=` aligns to the widest key (pacquet-resolving-parse-wanted-dependency, added by this PR). The merge from origin/main introduced a new dep that broke the uniform column alignment taplo enforces. - Update exec.rs's deferral doc: the workspace-projects-filter / workspace-projects-graph crates and the global --filter/--recursive flags landed on main (#11959, #12000), so the selection layer now exists; only the recursive runner is still missing. https://claude.ai/code/session_01PPwhryEFfN4iyZkVsjy2Hf
The perfectionist 0.0.0-rc.17 upgrade on main flags bare `#NNN` references as ambiguous (could be issue / PR / colour / etc.). Replace the two #11959 / #12000 references with explicit pull-request URLs, matching pacquet's existing doc-comment convention. https://claude.ai/code/session_01PPwhryEFfN4iyZkVsjy2Hf
Ports pnpm's
@pnpm/workspace.projects-graphand@pnpm/workspace.projects-filterto Rust and adds the--filter/-Fand--filter-prodCLI flags.What
pacquet-workspace-projects-graph—create_projects_graphbuilds the inter-project dependency graph (edges fromworkspace:, semver, and local-path specifiers).pacquet-workspace-projects-filter—parse_project_selector(name globs,...dependents/dependencies,^,!,{dir},[since]) andfilter_workspace_projects(selectors → selected projects).--filter/-Fand--filter-prodparse intoConfig::filter/Config::filter_prod, mirroring pnpm's CLI-only config.Scope (initial)
Selector parsing and project selection only. As with
--recursive, install still materializes every workspace importer in one shared pass, so narrowing the install to the selected projects is a follow-up — the selectors are stored but not yet consumed byinstall. The[since]changed-packages selector parses but returnsFilterError::UnsupportedDiffSelector(git-diff selection not ported yet).Tests
Ports the upstream
parseProjectSelector,filterWorkspaceProjects, andcreate_projects_graphsuites (diff-based cases stubbed asknown_failures);plans/TEST_PORTING.mdupdated.Written by an agent (Claude Code).
Summary by CodeRabbit
New Features
Utilities
Tests
Documentation