refactor(pacquet): optional DI pattern + documentation#11708
Conversation
Adapts pnpm/pacquet#517 (tracking pnpm/pacquet#339) into the in-tree pacquet subtree. The dependency-injection seam used to thread filesystem, environment, clock, and hostname capabilities through unit-test fakes is consolidated under a single naming convention: - Generic type parameter: `Api` → `Sys` (the "system seam" slot in function signatures that selects between the real OS and a fake). - Production provider struct: `RealApi` → `Host` (production is unqualified; fakes carry behaviour-based names). - Trait alignment: `FsReadString` → `FsReadToString` to match `std::fs::read_to_string` and the existing `modules-yaml` trait. - Test fakes: `*Api` suffix dropped (`FakeApi` → `FakeHostName`, `PermissionDeniedApi` → `PermissionDenied`, `EmptyReadApi` → `EmptyRead`). The eight principles (single-purpose traits, one composed generic parameter, static methods, associated types for data, traits on implementor, domain-neutral provider with domain-scoped traits, explicit turbofish in production, capabilities as primitives) are written up in CODE_STYLE_GUIDE.md alongside a worked `modules-yaml` example, and AGENTS.md links the rule into the "match pnpm" checklist so future ports follow the seam by default. Affected crates: `modules-yaml`, `cmd-shim`, `config`, `reporter`, `package-manager`, `cli`. No behaviour change — the rename is mechanical, every production call site retains the same turbofish shape (`::<Host>`), and every test fake declares the same bounds it did before.
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 Walkthrough<review_stack_artifact> </review_stack_artifact> 🚥 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 |
There was a problem hiding this comment.
Pull request overview
This PR ports the pacquet DI naming consolidation in-tree, standardizing the “system seam” generic parameter (Sys), the production provider (Host), and filesystem trait naming (FsReadToString) across pacquet crates, plus documenting the pattern in the style guide and checklist.
Changes:
- Renames DI generic parameter
Api→Sysand production providerRealApi→Hostacross crates and tests. - Aligns filesystem capability trait naming
FsReadString→FsReadToStringand updates all call sites/bounds. - Adds/links documentation for the DI pattern in
CODE_STYLE_GUIDE.mdandAGENTS.md.
Reviewed changes
Copilot reviewed 26 out of 26 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| pacquet/crates/reporter/src/tests.rs | Updates hostname capability fake/provider naming to FakeHostName/Host. |
| pacquet/crates/reporter/src/lib.rs | Renames production hostname provider RealApi → Host and updates cache init/docs. |
| pacquet/crates/package-manager/src/link_bins/tests.rs | Updates DI bounds to use FsReadToString. |
| pacquet/crates/package-manager/src/link_bins.rs | Renames DI seam Api → Sys, provider RealApi → Host, and updates trait bounds/calls. |
| pacquet/crates/package-manager/src/install/tests.rs | Switches modules-yaml provider turbofish from RealApi → Host. |
| pacquet/crates/package-manager/src/install.rs | Switches modules-yaml provider turbofish from RealApi → Host. |
| pacquet/crates/package-manager/src/install_without_lockfile.rs | Switches cmd-shim provider turbofish from RealApi → Host. |
| pacquet/crates/package-manager/src/install_frozen_lockfile.rs | Switches modules-yaml provider turbofish from RealApi → Host. |
| pacquet/crates/modules-yaml/tests/real_fs.rs | Switches modules-yaml provider turbofish from RealApi → Host. |
| pacquet/crates/modules-yaml/tests/index.rs | Switches modules-yaml provider turbofish from RealApi → Host. |
| pacquet/crates/modules-yaml/src/lib.rs | Renames production provider RealApi → Host and DI seam Api → Sys. |
| pacquet/crates/config/src/npmrc_auth.rs | Renames DI seam Api → Sys in .npmrc parsing/proxy cascade helpers. |
| pacquet/crates/config/src/lib.rs | Re-exports Host and renames DI seam Api → Sys in Config::current. |
| pacquet/crates/config/src/env_replace.rs | Renames DI seam Api → Sys and updates docs/tests helpers. |
| pacquet/crates/config/src/api.rs | Renames provider RealApi → Host and updates module docs. |
| pacquet/crates/cmd-shim/src/shim/tests.rs | Switches provider RealApi → Host and renames fakes away from *Api. |
| pacquet/crates/cmd-shim/src/shim.rs | Renames DI seam Api → Sys in shebang/runtime probing helpers. |
| pacquet/crates/cmd-shim/src/link_bins/tests.rs | Updates provider RealApi → Host and trait FsReadString → FsReadToString. |
| pacquet/crates/cmd-shim/src/link_bins.rs | Renames DI seam Api → Sys and trait FsReadString → FsReadToString. |
| pacquet/crates/cmd-shim/src/capabilities.rs | Renames provider RealApi → Host and capability trait FsReadString → FsReadToString. |
| pacquet/crates/cmd-shim/src/bin_resolver/tests.rs | Switches provider turbofish from RealApi → Host. |
| pacquet/crates/cmd-shim/src/bin_resolver.rs | Renames DI seam Api → Sys for walker-backed bin resolution. |
| pacquet/crates/cli/tests/install.rs | Updates docs/comments to refer to <Host as EnvVar>::var. |
| pacquet/crates/cli/src/cli_args.rs | Switches config provider turbofish from RealApi → Host and updates comment. |
| pacquet/CODE_STYLE_GUIDE.md | Adds detailed DI naming/pattern documentation and a worked example. |
| pacquet/AGENTS.md | Adds checklist item linking the DI seam rules in the style guide. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Copilot's review correctly pointed out that the worked `read_propagates_non_not_found_io_error` example in CODE_STYLE_GUIDE.md was wrong: it claimed the `FailingRead` fake could omit `impl Clock` because the test "never hits" the `prunedAt` branch, but Rust requires every bound in `Sys: FsReadToString + Clock` to be satisfied at the type level regardless of which body branch the call actually reaches. Fix the example to match the existing `modules-yaml` integration test (`tests/fakes.rs::read_propagates_non_not_found_io_error`): implement both traits, and stub the unused method with `unreachable!` so the bound is satisfied while the panic message documents the test's precondition.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@pacquet/CODE_STYLE_GUIDE.md`:
- Around line 767-772: The note around read_modules_manifest and FailingRead
incorrectly claims "bounds that gate dead branches are free"; Rust requires that
the generic parameter used in read_modules_manifest::<FailingRead> satisfy all
declared trait bounds (FsReadToString + Clock) at compile time. Update the
documentation to state that FailingRead must implement Clock as well (or change
the example to use a dummy type that implements both FsReadToString and Clock)
and mention that monomorphization requires all bounds regardless of which
branches (e.g., the prunedAt branch) are actually executed.
🪄 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: c2214ae7-7555-48fc-9718-5f22c3ff2dc5
📒 Files selected for processing (26)
pacquet/AGENTS.mdpacquet/CODE_STYLE_GUIDE.mdpacquet/crates/cli/src/cli_args.rspacquet/crates/cli/tests/install.rspacquet/crates/cmd-shim/src/bin_resolver.rspacquet/crates/cmd-shim/src/bin_resolver/tests.rspacquet/crates/cmd-shim/src/capabilities.rspacquet/crates/cmd-shim/src/link_bins.rspacquet/crates/cmd-shim/src/link_bins/tests.rspacquet/crates/cmd-shim/src/shim.rspacquet/crates/cmd-shim/src/shim/tests.rspacquet/crates/config/src/api.rspacquet/crates/config/src/env_replace.rspacquet/crates/config/src/lib.rspacquet/crates/config/src/npmrc_auth.rspacquet/crates/modules-yaml/src/lib.rspacquet/crates/modules-yaml/tests/index.rspacquet/crates/modules-yaml/tests/real_fs.rspacquet/crates/package-manager/src/install.rspacquet/crates/package-manager/src/install/tests.rspacquet/crates/package-manager/src/install_frozen_lockfile.rspacquet/crates/package-manager/src/install_without_lockfile.rspacquet/crates/package-manager/src/link_bins.rspacquet/crates/package-manager/src/link_bins/tests.rspacquet/crates/reporter/src/lib.rspacquet/crates/reporter/src/tests.rs
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
- GitHub Check: ubuntu-latest / Node.js 24 / Test
- GitHub Check: Run benchmark on ubuntu-latest
- GitHub Check: Run benchmark on ubuntu-latest
- GitHub Check: Code Coverage
- GitHub Check: Lint and Test (macos-latest)
- GitHub Check: Lint and Test (windows-latest)
🧰 Additional context used
📓 Path-based instructions (3)
pacquet/crates/**/*.rs
📄 CodeRabbit inference engine (pacquet/AGENTS.md)
Log emissions are part of 'match pnpm'—when porting a function that fires
pnpm:<channel>events throughglobalLogger/logger.debug(...)/streamParser.write(...), mirror the call site, payload, and ordering so@pnpm/cli.default-reporterparses pacquet's NDJSON the same way it parses pnpm's.Side-effecting code uses the dependency-injection seam—any code that touches the filesystem, environment variables, network, time, or process state goes through a capability trait on the
Hostprovider, threaded asSys: <Bounds>through the function signature. Follow the eight principles andmodules-yamlworked example documented in CODE_STYLE_GUIDE.md.Declare a newtype wrapper for branded string types instead of collapsing the brand into plain
Stringor&str. Give the type its own struct so misuse is a type error.If upstream pnpm always validates before constructing a branded string type, validate too. Pacquet's wrapper must construct only via
TryFrom<String>and/orFromStr. Do not provide an infallible public constructor that takes an arbitrary string.If upstream pnpm never validates a branded string type, just brand for type-safety. Expose an infallible
From<String>(andFrom<&str>when convenient). The type-safety win is the whole point.If upstream pnpm occasionally constructs a branded string type without validation, expose
from_str_unchecked. Add afrom_str_unchecked(or similarly named) constructor on the Rust side so callers can opt into the same unchecked path explicitly. Keep the validating constructor as well.Match upstream pnpm serde behavior for branded types that cross JSON, YAML, or INI boundaries. Use
#[serde(try_from = "String")]for deserialization and#[serde(into = "String")]for serialization. Use both when the type is round-tripped.Derive simple conversions with
derive_morewhen the conversion impls are mechanical one-liners that wrap or unwrap the inner field. Use#[derive(derive_more::From)]and `#[derive(de...
Files:
pacquet/crates/package-manager/src/install_without_lockfile.rspacquet/crates/reporter/src/lib.rspacquet/crates/cli/tests/install.rspacquet/crates/modules-yaml/tests/index.rspacquet/crates/config/src/api.rspacquet/crates/cli/src/cli_args.rspacquet/crates/reporter/src/tests.rspacquet/crates/config/src/env_replace.rspacquet/crates/modules-yaml/tests/real_fs.rspacquet/crates/package-manager/src/install/tests.rspacquet/crates/cmd-shim/src/capabilities.rspacquet/crates/package-manager/src/link_bins/tests.rspacquet/crates/package-manager/src/install.rspacquet/crates/package-manager/src/install_frozen_lockfile.rspacquet/crates/cmd-shim/src/shim.rspacquet/crates/cmd-shim/src/bin_resolver.rspacquet/crates/config/src/npmrc_auth.rspacquet/crates/cmd-shim/src/shim/tests.rspacquet/crates/modules-yaml/src/lib.rspacquet/crates/config/src/lib.rspacquet/crates/package-manager/src/link_bins.rspacquet/crates/cmd-shim/src/bin_resolver/tests.rspacquet/crates/cmd-shim/src/link_bins/tests.rspacquet/crates/cmd-shim/src/link_bins.rs
pacquet/**/AGENTS.md
📄 CodeRabbit inference engine (pacquet/CLAUDE.md)
Document agent functionality and interactions in AGENTS.md
pacquet/**/AGENTS.md: Document agent responsibilities, capabilities, and interactions in AGENTS.md file
Maintain clear and up-to-date documentation of all agent definitions and their purposes
Include agent capabilities, input/output specifications, and integration guidelines in agent documentation
Files:
pacquet/AGENTS.md
pacquet/crates/**/tests/**/*.rs
📄 CodeRabbit inference engine (pacquet/AGENTS.md)
Tests live alongside the code they exercise (standard Cargo layout) plus integration tests under each crate's
tests/. Shared test fixtures live undercrates/testing-utils/src/fixtures/.When porting behavior from pnpm, port the relevant pnpm tests too (as Rust tests) whenever they translate. Matching test coverage is the easiest way to prove behavioral parity.
Follow the test-logging guidance in the style guide—log before non-
assert_eq!assertions,dbg!complex structures, skip logging for simple scalarassert_eq!.
Files:
pacquet/crates/cli/tests/install.rspacquet/crates/modules-yaml/tests/index.rspacquet/crates/modules-yaml/tests/real_fs.rs
🧠 Learnings (2)
📓 Common learnings
Learnt from: CR
Repo: pnpm/pnpm
Timestamp: 2026-05-18T00:00:56.448Z
Learning: Any change in pacquet must match how the same feature is implemented in the TypeScript pnpm CLI. Before writing code, find the equivalent code in the TypeScript pnpm workspaces, read the implementation, and port the behavior faithfully with structural similarity.
Learnt from: CR
Repo: pnpm/pnpm
Timestamp: 2026-05-18T00:00:56.448Z
Learning: When citing code in comments, doc comments, Markdown docs, PR descriptions, or commit messages, link to a specific commit SHA (first 10 hex characters), not branch names. Branch links are impermanent and may drift or 404; commit links remain meaningful long after code changes.
Learnt from: CR
Repo: pnpm/pnpm
Timestamp: 2026-05-18T00:00:56.448Z
Learning: Follow the contributing guide in CONTRIBUTING.md, which covers commit message format, writing style, setup, and automated checks to run before committing.
Learnt from: CR
Repo: pnpm/pnpm
Timestamp: 2026-05-18T00:00:56.448Z
Learning: Follow the code style guide in CODE_STYLE_GUIDE.md, which covers code-level conventions not enforced by tooling: imports, modules, naming, ownership and borrowing, parameter type selection, trait bounds, pattern matching, `pipe-trait`, error handling, test layout, logging during tests, and cloning of `Arc` and `Rc`.
Learnt from: CR
Repo: pnpm/pnpm
Timestamp: 2026-05-18T00:00:56.448Z
Learning: Run `just ready` to run the same checks CI runs (typos, fmt, check, test, lint) before declaring a task complete.
Learnt from: CR
Repo: pnpm/pnpm
Timestamp: 2026-05-18T00:00:56.448Z
Learning: Keep commits focused. A bug fix commit should not also refactor or reformat unrelated code.
Learnt from: CR
Repo: pnpm/pnpm
Timestamp: 2026-05-18T00:00:56.448Z
Learning: Reference the upstream pnpm commit/PR you ported from, when applicable.
Learnt from: CR
Repo: pnpm/pnpm
Timestamp: 2026-05-18T00:00:56.448Z
Learning: Run `just ready` before pushing to ensure all checks pass.
Learnt from: CR
Repo: pnpm/pnpm
Timestamp: 2026-05-18T00:00:56.448Z
Learning: Use Conventional Commits format with scope naming the crate or area being touched. Pacquet adds a `bench` type for benchmark-only changes.
Learnt from: CR
Repo: pnpm/pnpm
Timestamp: 2026-05-18T00:00:56.448Z
Learning: Do not add features, flags, or behaviors that pnpm does not have.
Learnt from: CR
Repo: pnpm/pnpm
Timestamp: 2026-05-18T00:00:56.448Z
Learning: Do not change lockfile format, store layout, `.npmrc` semantics, or CLI surface unless pnpm changed them first.
Learnt from: CR
Repo: pnpm/pnpm
Timestamp: 2026-05-18T00:00:56.448Z
Learning: Do not add a dependency that is not already declared in the workspace without an explicit human request. If there is a clear benefit and justification for pulling in a new third-party crate, ask the human to approve it and to add it to `[workspace.dependencies]`.
Learnt from: CR
Repo: pnpm/pnpm
Timestamp: 2026-05-18T00:00:56.448Z
Learning: Do not disable lints, tests, or CI checks to make a PR green.
📚 Learning: 2026-05-14T17:38:34.573Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11640
File: pacquet/AGENTS.md:1-6
Timestamp: 2026-05-14T17:38:34.573Z
Learning: In this repository, files named `AGENTS.md` are agent-guide instructions for AI coding tools (e.g., Claude Code/Codex/Cursor) following the agentsmd.net convention, not documentation of software agents or an agentic framework. When reviewing `**/AGENTS.md`, do not apply review rules intended for agentic-framework documentation (e.g., documenting agent capabilities, inputs/outputs, or integration points); instead, treat these files as tooling/assistive guidance for contributors.
Applied to files:
pacquet/AGENTS.md
🔇 Additional comments (26)
pacquet/crates/reporter/src/tests.rs (1)
9-11: LGTM!Also applies to: 808-814, 817-823
pacquet/crates/cmd-shim/src/link_bins.rs (1)
4-5: LGTM!Also applies to: 190-204, 206-214, 239-255, 262-267, 295-313, 339-348, 418-421, 447-448, 481-493, 501-507, 511-512, 525-526, 560-560
pacquet/crates/cmd-shim/src/bin_resolver.rs (1)
66-67: LGTM!Also applies to: 77-77, 90-90, 102-102
pacquet/crates/cmd-shim/src/shim.rs (1)
47-47: LGTM!Also applies to: 50-50, 62-64, 88-91
pacquet/crates/cli/src/cli_args.rs (1)
12-12: LGTM!Also applies to: 97-103
pacquet/crates/cli/tests/install.rs (1)
256-268: LGTM!pacquet/crates/config/src/env_replace.rs (1)
25-27: LGTM!Also applies to: 33-47, 95-95, 143-145
pacquet/crates/config/src/lib.rs (1)
8-8: LGTM!Also applies to: 770-777, 814-815, 822-823, 955-955, 1027-1028, 1045-1046, 1069-1070, 1086-1087, 1101-1102, 1120-1121, 1138-1139, 1151-1152, 1176-1177, 1204-1205, 1229-1230, 1267-1268, 1287-1287, 1323-1324, 1347-1348, 1376-1377, 1413-1414, 1454-1455, 1490-1491
pacquet/crates/config/src/npmrc_auth.rs (1)
146-147: LGTM!Also applies to: 162-163, 311-319, 325-336, 394-397
pacquet/crates/cmd-shim/src/bin_resolver/tests.rs (1)
2-2: LGTM!Also applies to: 14-14, 23-23, 37-37, 55-55, 69-69, 76-76, 104-104, 125-125, 143-143, 158-158, 179-179, 192-192, 205-205, 245-245, 280-280, 309-309, 324-324, 345-345, 360-360, 416-416, 437-437, 500-500
pacquet/crates/cmd-shim/src/link_bins/tests.rs (1)
4-5: LGTM!Also applies to: 48-48, 97-97, 125-125, 149-149, 162-162, 178-178, 214-214, 240-240, 262-262, 269-269, 296-296, 304-304, 312-312, 330-330, 399-399, 468-468, 540-540, 614-614, 684-684, 753-753, 830-830, 861-861, 936-936, 990-990, 1043-1043, 1085-1085, 1140-1140, 1188-1188, 1226-1226, 1269-1269
pacquet/crates/cmd-shim/src/shim/tests.rs (1)
6-6: LGTM!Also applies to: 253-253, 262-262, 274-274, 286-286, 295-296, 301-301, 312-313, 319-319, 323-323, 327-327, 337-337, 342-342, 348-348, 364-364, 379-379
pacquet/crates/modules-yaml/src/lib.rs (1)
68-68: LGTM!Also applies to: 70-70, 77-77, 84-84, 91-91, 365-365, 369-369, 371-371, 374-374, 389-389, 410-410, 412-412, 417-417, 429-429, 434-434
pacquet/crates/package-manager/src/install.rs (1)
14-15: LGTM!Also applies to: 473-473
pacquet/crates/package-manager/src/install_without_lockfile.rs (1)
10-10: LGTM!Also applies to: 210-210
pacquet/crates/package-manager/src/link_bins.rs (1)
6-6: LGTM!Also applies to: 64-64, 123-123, 248-248, 252-252, 256-256, 258-258, 260-260, 271-271, 284-284, 349-349, 357-358, 470-470, 487-487, 498-498, 529-529, 531-531, 533-533, 541-541, 568-568, 572-572, 592-592, 630-630, 636-636, 638-638, 648-648, 672-672, 683-683, 693-693, 702-702, 705-705, 709-709
pacquet/crates/package-manager/src/link_bins/tests.rs (1)
429-429: LGTM!Also applies to: 445-445
pacquet/crates/modules-yaml/tests/index.rs (1)
8-8: LGTM!Also applies to: 47-48, 72-72, 102-102, 154-155, 165-165
pacquet/crates/modules-yaml/tests/real_fs.rs (1)
11-11: LGTM!Also applies to: 34-34, 56-56, 78-78, 100-100, 137-137, 182-183, 212-212
pacquet/crates/package-manager/src/install/tests.rs (1)
5-5: LGTM!Also applies to: 659-659, 1831-1831, 1863-1863, 1986-1986, 2177-2177, 2426-2426
pacquet/crates/package-manager/src/install_frozen_lockfile.rs (1)
21-21: LGTM!Also applies to: 341-341
pacquet/AGENTS.md (1)
45-55: LGTM!pacquet/CODE_STYLE_GUIDE.md (1)
644-766: LGTM!Also applies to: 777-813
pacquet/crates/cmd-shim/src/capabilities.rs (1)
2-3: LGTM!Also applies to: 30-31, 51-53, 148-150, 161-171, 173-180, 182-197, 199-209, 212-224, 227-241
pacquet/crates/config/src/api.rs (1)
1-1: LGTM!Also applies to: 8-8, 15-15, 38-38, 41-41, 46-49
pacquet/crates/reporter/src/lib.rs (1)
744-745: LGTM!Also applies to: 756-760, 766-768
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #11708 +/- ##
==========================================
- Coverage 88.95% 88.93% -0.03%
==========================================
Files 129 129
Lines 14845 14845
==========================================
- Hits 13206 13202 -4
- Misses 1639 1643 +4 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Micro-Benchmark ResultsLinux |
Integrated-Benchmark Report (Linux)Scenario: Frozen Lockfile
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 2.3491845433000003,
"stddev": 0.0863811900833396,
"median": 2.3509419887,
"user": 2.69679976,
"system": 3.5707213799999993,
"min": 2.2481829426999997,
"max": 2.5501665017,
"times": [
2.3153134577,
2.3482980277,
2.3535859497,
2.2600796037,
2.5501665017,
2.2833114627,
2.3987850507,
2.3643597376999996,
2.3697626987,
2.2481829426999997
]
},
{
"command": "pacquet@main",
"mean": 2.2961128775999997,
"stddev": 0.0772293857224174,
"median": 2.2895478586999998,
"user": 2.71343566,
"system": 3.5935678799999993,
"min": 2.1926006037,
"max": 2.4783485897,
"times": [
2.2381454376999996,
2.3078017547,
2.4783485897,
2.1926006037,
2.3259878147,
2.3099942377,
2.2712939626999997,
2.3276456497,
2.2625595787,
2.2467511467
]
},
{
"command": "pnpm",
"mean": 4.590383639299999,
"stddev": 0.04779833478184206,
"median": 4.5908232952,
"user": 7.837600760000001,
"system": 4.04380328,
"min": 4.5055280317,
"max": 4.6607061607,
"times": [
4.6607061607,
4.6403987297,
4.6038518097,
4.5917766117,
4.5055280317,
4.5560006587,
4.5686312917,
4.5898699787,
4.5485029327,
4.6385701877
]
}
]
}Scenario: Frozen Lockfile (Hot Cache)
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 0.68974985068,
"stddev": 0.020539116597723176,
"median": 0.68524348038,
"user": 0.37281397999999993,
"system": 1.5769578,
"min": 0.66790304688,
"max": 0.74252446388,
"times": [
0.74252446388,
0.69952321488,
0.6780371568800001,
0.6817096358800001,
0.66790304688,
0.68662558488,
0.6838613758800001,
0.6762436188800001,
0.6926348748800001,
0.6884355338800001
]
},
{
"command": "pacquet@main",
"mean": 0.7295345063800001,
"stddev": 0.07057293550772524,
"median": 0.6972351188800001,
"user": 0.3663752799999999,
"system": 1.5903347999999997,
"min": 0.6656222928800001,
"max": 0.8346586038800001,
"times": [
0.7399015498800001,
0.8244197948800001,
0.6676532698800001,
0.6656222928800001,
0.67362589888,
0.8346586038800001,
0.7153483108800001,
0.6791219268800001,
0.6759589728800001,
0.81903444288
]
},
{
"command": "pnpm",
"mean": 2.3438480114799995,
"stddev": 0.058490664867363255,
"median": 2.32921374238,
"user": 2.9269087799999993,
"system": 2.1721015,
"min": 2.27901820288,
"max": 2.46773163188,
"times": [
2.46773163188,
2.34345947688,
2.31960448888,
2.42204521688,
2.33210015788,
2.3210430138800002,
2.32632732688,
2.27901820288,
2.28408178688,
2.34306881188
]
}
]
} |
The repository's style guide is read as the project's own rules; the historical PRs and tracking issue that prompted the consolidation are not part of those rules and would only invite readers to chase context that doesn't change the conventions themselves.
Remove sentences that contrast the chosen names against ones that no longer appear anywhere in the codebase (the 'Api / Provider / Env / Fs' alternatives for the generic parameter, the 'Real* / Prod* Platonic prefix' for the production provider, the '*Api / *Sys / *Host' suffix prohibition for fakes, and the external 'FsApi' / 'parallel-disk-usage' anti-pattern reference). A reader of the present tree has no context for the displaced names, and the style guide is meant to be read as the project's own rules rather than as a record of the route taken to them.
…s they can't cover Reshape the DI section so the gating rule leads. Real fixtures (`tempfile::TempDir`, the mocked registry, integration tests against the actual binary) are the default for happy paths and most error paths. The DI seam is reserved for: - filesystem error kinds the host OS won't reproduce portably (`PermissionDenied`, `ENOSPC`, mid-walk disappearance, …), - deterministic time, and - external-service happy paths that can't be staged in CI — pnpm's `login` 2FA round-trip, `publish` OIDC / provenance, and similar. (None of these features are ported to pacquet yet; the exception is documented so the convention is in place when they land.) Drop the trailing "When to use real fixtures instead" subsection since its content is now folded into the opening paragraphs, and update AGENTS.md item 7 to match so the cardinal-rule checklist does not contradict the style guide.
…waving at it The four-bullet 'Reach for it only when one of the following applies' list is the well-defined gating rule; the prior 'Most new code should not introduce a DI seam at all' sentence and the 'right tool for every happy path and for the bulk of error paths' phrasing both gestured at a quantitative criterion the bullets already make precise by negation. Drop the vague sentence and reword the preceding one to point forward to the enumeration.
| //! Capability traits and the project-wide [`Host`] provider. | ||
| //! | ||
| //! Mirrors the dependency-injection pattern documented in | ||
| //! [pnpm/pacquet#339](https://github.com/pnpm/pacquet/issues/339): one | ||
| //! trait per capability, one provider gathering every capability impl | ||
| //! used across the codebase, all methods static. Production callers | ||
| //! turbofish the real provider explicitly | ||
| //! (e.g. `Config::current::<RealApi>(...)`); tests substitute a | ||
| //! (e.g. `Config::current::<Host>(...)`); tests substitute a | ||
| //! per-test unit struct that implements only the bounds the function | ||
| //! actually declares, with any per-test scenario data stored in a | ||
| //! `static` inside the test fn. | ||
| //! | ||
| //! Today the provider only exposes [`EnvVar`]. As more side-effecting | ||
| //! capabilities are introduced (filesystem, disk inspection, time, | ||
| //! …) their `impl … for RealApi` blocks land here too, so callers | ||
| //! …) their `impl … for Host` blocks land here too, so callers | ||
| //! never juggle multiple providers. Trait names keep their domain |
There was a problem hiding this comment.
Fixed in a13edfc. The module doc now scopes the provider to pacquet-config (which is the actual reality — each crate that needs a capability seam declares its own Host), drops the misleading "project-wide" / "callers never juggle multiple providers" claims, and points to the style-guide section as the canonical convention rather than the now-stale external issue.
Written by an agent (Claude Code, claude-opus-4-7).
Generated by Claude Code
| /// [`link_bins_of_packages`] propagates a non-`NotFound` `read_dir` | ||
| /// error from the calling context. Use a fake `Api` that fails the | ||
| /// error from the calling context. Use a fake `Sys` that fails the | ||
| /// initial `create_dir_all` to cover the [`LinkBinsError::CreateBinDir`] | ||
| /// error variant that real fs can't trigger portably. |
There was a problem hiding this comment.
Good catch — fixed in a13edfc. The doc comment now describes the create_dir_all failure path that the fake actually drives, matching the assertion on LinkBinsError::CreateBinDir.
Written by an agent (Claude Code, claude-opus-4-7).
Generated by Claude Code
There was a problem hiding this comment.
🧹 Nitpick comments (1)
pacquet/CODE_STYLE_GUIDE.md (1)
796-809: ⚡ Quick winClarify the stateful-fake example with a compilable
SystemTimeinitialization.Line 798's
static FAKE_NOW: SystemTime = /* fixed instant */;is pseudo-code that won't compile—SystemTimehas noconstconstructor for arbitrary instants. Readers copying the example will hit a compile error. Consider replacing the placeholder with a working pattern to make the example self-contained.📝 Suggested working example
#[test] fn read_fills_in_pruned_at_when_missing() { - static FAKE_NOW: SystemTime = /* fixed instant */; + static FAKE_NOW: SystemTime = SystemTime::UNIX_EPOCH; struct FakeClock; impl Clock for FakeClock { fn now() -> SystemTime { FAKE_NOW } } - // The `static` lives in this fn's scope, so other tests get - // independent storage and never race on it. The provider type - // stays an empty unit struct; the state lives next to the test - // that needs it. + // Each test fn declares its own distinct static (even when named + // identically), so other tests get independent storage and never + // race. The provider type stays an empty unit struct; state lives + // next to the test that needs it. // ... }Alternatively, if you want to demonstrate a non-epoch instant, show the
lazy_staticoronce_cell::sync::Lazypattern explicitly, thoughUNIX_EPOCHis simpler and sufficient for the example's pedagogical purpose.🤖 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/CODE_STYLE_GUIDE.md` around lines 796 - 809, The example's static FAKE_NOW initialization is non-compilable; update the test read_fills_in_pruned_at_when_missing() so FAKE_NOW is created with a real, compilable value (e.g., use SystemTime::UNIX_EPOCH for a const static) or switch to a runtime initializer (once_cell::sync::Lazy or lazy_static) if you need a non-epoch instant; keep the FakeClock impl and Clock trait use but replace the placeholder with one of these concrete patterns so the example compiles and remains stateful and test-local.
🤖 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.
Nitpick comments:
In `@pacquet/CODE_STYLE_GUIDE.md`:
- Around line 796-809: The example's static FAKE_NOW initialization is
non-compilable; update the test read_fills_in_pruned_at_when_missing() so
FAKE_NOW is created with a real, compilable value (e.g., use
SystemTime::UNIX_EPOCH for a const static) or switch to a runtime initializer
(once_cell::sync::Lazy or lazy_static) if you need a non-epoch instant; keep the
FakeClock impl and Clock trait use but replace the placeholder with one of these
concrete patterns so the example compiles and remains stateful and test-local.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 600002ad-03b3-48ec-a014-5689c8e3d2e3
📒 Files selected for processing (2)
pacquet/AGENTS.mdpacquet/CODE_STYLE_GUIDE.md
✅ Files skipped from review due to trivial changes (1)
- pacquet/AGENTS.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). (2)
- GitHub Check: Agent
- GitHub Check: ubuntu-latest / Node.js 24 / Test
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
Repo: pnpm/pnpm
Timestamp: 2026-05-18T00:55:36.817Z
Learning: Pacquet behavior must match the equivalent pnpm TypeScript implementation - study pnpm code before writing features, and reconcile toward pnpm as the source of truth
Learnt from: CR
Repo: pnpm/pnpm
Timestamp: 2026-05-18T00:55:36.817Z
Learning: Use real fixtures (tempfile::TempDir, mocked registry, integration tests with actual binary) for testing; reserve dependency-injection seams only for branches that cannot be covered by real fixtures (filesystem error kinds, deterministic time, external-service paths like 2FA/OIDC)
Learnt from: CR
Repo: pnpm/pnpm
Timestamp: 2026-05-18T00:55:36.817Z
Learning: Use only the first 10 hex characters of commit SHAs when citing code in comments, doc comments, Markdown docs, PR descriptions, or commit messages - never use branch names for GitHub links as they are impermanent
Learnt from: CR
Repo: pnpm/pnpm
Timestamp: 2026-05-18T00:55:36.817Z
Learning: Do not break existing method chains (including pipe-trait .pipe(...) chains) into intermediate let bindings unless justified by compilation failure, borrow checker rejection, meaningful performance gains, or concrete necessity - refactoring for style alone is not justified
Learnt from: CR
Repo: pnpm/pnpm
Timestamp: 2026-05-18T00:55:36.817Z
Learning: Choose owned vs. borrowed parameters to minimize copies; prefer most-encompassing types (&Path over &PathBuf, &str over &String) when it does not force extra copies
Learnt from: CR
Repo: pnpm/pnpm
Timestamp: 2026-05-18T00:55:36.817Z
Learning: Commit messages must follow Conventional Commits format with scope naming the crate or area (use git log --oneline for examples); scope examples: network, lockfile, store-dir, package-manifest, fs, executor, diagnostics
Learnt from: CR
Repo: pnpm/pnpm
Timestamp: 2026-05-18T00:55:36.817Z
Learning: Use bench as a commit message type for benchmark-only changes
Learnt from: CR
Repo: pnpm/pnpm
Timestamp: 2026-05-18T00:55:36.817Z
Learning: Do not add features, flags, or behaviors that pnpm does not have
Learnt from: CR
Repo: pnpm/pnpm
Timestamp: 2026-05-18T00:55:36.817Z
Learning: Do not change lockfile format, store layout, .npmrc semantics, or CLI surface unless pnpm changed them first
Learnt from: CR
Repo: pnpm/pnpm
Timestamp: 2026-05-18T00:55:36.817Z
Learning: Do not add workspace dependencies to individual crates - dependencies must be declared in [workspace.dependencies] at the repo root Cargo.toml before being used
Learnt from: CR
Repo: pnpm/pnpm
Timestamp: 2026-05-18T00:55:36.817Z
Learning: Do not introduce unsafe code without clear justification and review
Learnt from: CR
Repo: pnpm/pnpm
Timestamp: 2026-05-18T00:55:36.817Z
Learning: Port relevant pnpm tests from TypeScript whenever they translate - matching test coverage is the easiest way to prove behavioral parity; consult plans/TEST_PORTING.md for the active porting plan and conventions (known_failures, pacquet_testing_utils::allow_known_failure!, regression verification)
Learnt from: CR
Repo: pnpm/pnpm
Timestamp: 2026-05-18T00:55:36.817Z
Learning: When snapshot tests via insta change intentionally, review the diff carefully before accepting with cargo insta review - never accept snapshot changes blindly
Learnt from: CR
Repo: pnpm/pnpm
Timestamp: 2026-05-18T00:55:36.817Z
Learning: Keep commits focused - a bug fix commit should not also refactor or reformat unrelated code
Learnt from: CR
Repo: pnpm/pnpm
Timestamp: 2026-05-18T00:55:36.817Z
Learning: Reference the upstream pnpm commit/PR when porting behavior
Learnt from: CR
Repo: pnpm/pnpm
Timestamp: 2026-05-18T00:55:36.817Z
Learning: Run just ready before pushing - the pre-push hook runs rustfmt and taplo, so your environment must be able to run cargo
🪛 LanguageTool
pacquet/CODE_STYLE_GUIDE.md
[grammar] ~652-~652: Ensure spelling is correct
Context: ...s faking, not just the error path. When pacquet ports those features, DI is the right tool fo...
(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)
🔇 Additional comments (2)
pacquet/CODE_STYLE_GUIDE.md (2)
644-664: LGTM!
811-823: LGTM!
- crates/cmd-shim/src/link_bins/tests.rs: the doc comment on `link_bins_propagates_create_bin_dir_error_via_di` described a `read_dir` error path, but the fake fails `create_dir_all` to cover `LinkBinsError::CreateBinDir`. Reword to match the branch actually exercised. - crates/config/src/api.rs: drop the "project-wide" / "callers never juggle multiple providers" framing — every crate that needs a capability seam declares its own `Host`, so the claim is wrong. Reword to scope the doc to `pacquet-config`, drop the now-stale external issue link, and point to the style-guide section as the canonical convention. - CODE_STYLE_GUIDE.md: the stateful-fake worked example used `static FAKE_NOW: SystemTime = /* fixed instant */;`, which does not compile. Replace with `SystemTime::UNIX_EPOCH` (a `const`, usable in a `static`) and note `LazyLock` as the runtime-init alternative when a non-epoch instant is needed.
| //! [Dependency injection for tests](../../../CODE_STYLE_GUIDE.md#dependency-injection-for-tests) | ||
| //! section of the style guide for the full convention. |
There was a problem hiding this comment.
mod api is private in pacquet-config (only EnvVar and Host are pub use-re-exported into the crate root), so the //! module-level docs never render in cargo doc output and the relative ../../../CODE_STYLE_GUIDE.md link only appears in source-side reading — where the relative path resolves correctly. The link is intentional for that audience; the re-exported items (EnvVar, Host) carry their own /// docs which don't include the relative link.
Written by an agent (Claude Code, claude-opus-4-7).
Generated by Claude Code
| /// Project-wide capability provider. Production code threads | ||
| /// `RealApi` through generic call sites with an explicit turbofish: | ||
| /// `Host` through generic call sites with an explicit turbofish: | ||
| /// | ||
| /// ```ignore | ||
| /// let config = Config::current::<RealApi>(env::current_dir, home::home_dir, Default::default); | ||
| /// let config = Config::current::<Host>(env::current_dir, home::home_dir, Default::default); | ||
| /// ``` |
There was a problem hiding this comment.
Good catch — fixed in 0f8309c. The Host doc now reads "Production provider for the capability traits in this crate." to match the module-level scoping.
Written by an agent (Claude Code, claude-opus-4-7).
Generated by Claude Code
| pub fn apply_proxy_cascade<Sys: EnvVar>(&mut self, config: &mut Config) { | ||
| // Upstream's `getProcessEnv` tries literal-, upper-, and | ||
| // lower-case in order (config/reader/src/index.ts:689-693). For | ||
| // the proxy var names below the literal form is already either | ||
| // fully upper or fully lower, so the triple collapses to two | ||
| // real attempts. | ||
| fn env_pair<Api: EnvVar>(upper: &str, lower: &str) -> Option<String> { | ||
| Api::var(upper).or_else(|| Api::var(lower)) | ||
| fn env_pair<Sys: EnvVar>(upper: &str, lower: &str) -> Option<String> { | ||
| Sys::var(upper).or_else(|| Sys::var(lower)) | ||
| } |
There was a problem hiding this comment.
Two reasons I'm leaving this as-is:
-
The shadowing predates this PR. The inner
fn env_pair<Api: EnvVar>was already there before the rename; this PR only retitledApi→Sys. Fixing the structure is out of scope for a mechanical rename. -
The suggestion as worded doesn't compile. Inner
fnitems in Rust don't capture from the enclosing function's generic parameters — that's a language constraint, not a style choice. The workable fix is convertingenv_pairto a closure (which does captureSys), but that's a refactor of pre-existing code I'd rather leave for its own focused commit so the diff doesn't muddle the rename PR.
Written by an agent (Claude Code, claude-opus-4-7).
Generated by Claude Code
| /// Capability for obtaining the host name written into the [bunyan]-shaped | ||
| /// envelope. | ||
| /// | ||
| /// Backed by a real syscall in production via [`RealApi`]. Tests can supply | ||
| /// Backed by a real syscall in production via [`Host`]. Tests can supply | ||
| /// their own implementation when behavior depends on the value. | ||
| /// |
There was a problem hiding this comment.
Good catch — clarified in 0f8309c. The GetHostName doc now records the actual shape: the bunyan envelope reads from the cached static HOSTNAME (initialized by Host::get_host_name), so the trait is not a seam into envelope-rendered output. It exists to keep the syscall behind a named capability and to let unit tests exercise it in isolation (tests::get_host_name_capability_is_mockable). Plumbing Sys: GetHostName through the emission site to substitute per-test would be a separate change.
Written by an agent (Claude Code, claude-opus-4-7).
Generated by Claude Code
- crates/config/src/api.rs: the doc on `Host` still called it a "Project-wide capability provider", which contradicts the module-level docs (each crate has its own `Host`). Scope the line to "Production provider for the capability traits in this crate." - crates/reporter/src/lib.rs: the `GetHostName` trait docs implied a test fake could substitute the hostname rendered into the bunyan envelope, but the envelope reads from the process-cached `static HOSTNAME` initialized by `Host::get_host_name` — the emission path is not generic over the trait. Document the actual shape: the trait keeps the syscall behind a named seam and lets unit tests exercise the capability in isolation; substituting per test would require plumbing `Sys: GetHostName` through the emission site, which has not been done.
The previous commit referenced the private `HOSTNAME` static via an intra-doc link (`[\`HOSTNAME\`]`) inside the doc comment on the public `GetHostName` trait, which rustdoc rejects under `-D warnings` (`rustdoc::private-intra-doc-links`) — the link would break in the rendered docs because the target is private. Swap the link for plain backticks and drop the reference to the private `tests::get_host_name_capability_is_mockable` test fn, which a docs.rs reader cannot navigate to either.
| The dependency-injection seam described below is the **narrow second route**. Reach for it only when one of the following applies: | ||
|
|
||
| - **Filesystem error branches the host OS won't reproduce portably.** `PermissionDenied`, `ENOSPC`, a directory that disappears mid-walk, a chmod that fails after the file exists — provoking these on real disks is platform-specific, racy, or both. A fake that returns the exact `io::ErrorKind` is the only portable way to drive the branch. | ||
| - **Deterministic time.** Asserting that `prunedAt` equals a specific RFC 2822 timestamp, or that a throttled emitter fires on the second sample, needs the clock to be a known value. The real `SystemTime::now` makes those assertions flaky. |
There was a problem hiding this comment.
Good catch — fixed in f35b23d. The line now says "HTTP-date (RFC 7231 IMF-fixdate, what httpdate::fmt_http_date emits)" so readers know which timestamp grammar the example is asserting against.
Written by an agent (Claude Code, claude-opus-4-7).
Generated by Claude Code
The style guide called `prunedAt` an 'RFC 2822 timestamp', but the code stamps it via `httpdate::fmt_http_date`, which produces the RFC 7231 / RFC 1123 IMF-fixdate (HTTP-date) format — a different grammar. Correct the wording so readers know which format the deterministic-time example is actually asserting against.
…ame (#11708) PR #11708 renamed the dependency-injection seam (`Api` → `Sys`, `RealApi` → `Host`, `FsReadString` → `FsReadToString`, fakes drop the `*Api` suffix) and documented the convention in `pacquet/CODE_STYLE_GUIDE.md`. My tests on this branch use real fixtures only (no DI fakes), so the rename has no source-level impact on the tests themselves; the documentation references in the PR description and the per-file commit message get updated separately. https://claude.ai/code/session_01D8WBTfQzTpsZsRknrzwNKL
… in new test fixtures origin/main advanced with four PRs touching files this branch also changes: - #11679 (fix: silence verify-deps auto-install output) — TS only, no conflict with pacquet/. - #11705 (feat: tighten minimumReleaseAge) — TS only, no conflict. - #11708 (refactor(pacquet): optional DI pattern + documentation) — renames the test-DI seam (`Api` generic → `Sys`, `RealApi` → `Host`, `FsReadString` → `FsReadToString`, fakes drop `*Api` suffix) across modules-yaml / cmd-shim / config / reporter / package-manager / cli, and adds a "Dependency injection for tests" section to `pacquet/CODE_STYLE_GUIDE.md` plus a corresponding rule 7 in `pacquet/AGENTS.md`. Most files auto-merged cleanly with this branch's single-letter renames; only `pacquet/crates/patching/Cargo.toml` needed a manual resolve where this branch added `[lints] workspace = true` and main added `text-block-macros` to dev-dependencies — combined. - #11710 (test(pacquet): coverage) — adds 44 unit + integration tests. Two of the new closures (`|v|` in `registry/package/tests.rs:84` and `|e|` in `lockfile/save_lockfile/tests.rs:337`) tripped `perfectionist::single_letter_closure_param` because they predate this branch's enablement of the rule; renamed to `|version|` / `|entry|` to keep dylint at zero warnings post-merge. Verified: `cargo check --workspace --tests --locked` clean, `cargo dylint --all -- --all-targets --workspace` clean, `cargo fmt --check` clean.
Commit 49c690c inlined the SmartDefault expression for `Config::store_dir` and dropped the `default_store_dir_host` wrapper, but three comments still pointed at the wrapper or at the old single-type-parameter signature of `default_store_dir`. CodeRabbit and Copilot flagged all three on review: - `crates/config/src/lib.rs`, `have_default_values`: the comment said the SmartDefault resolved "via the thin `default_store_dir_host` wrapper". Now reflects the direct `default_store_dir::<Host, _, _, _>(home::home_dir, env::current_dir)` call. - `crates/config/src/npmrc_auth/tests.rs`, `ignores_non_auth_keys`: the comment cited `default_store_dir::<Host>` with no closure parameters. Now spells out the four-parameter turbofish that matches the current signature. - `CODE_STYLE_GUIDE.md`, "Reading process state": the paragraph recommended an args-less `default_store_dir_host` wrapper as the pattern to follow. Now recommends inlining the production composition at the `SmartDefault` site directly. Copilot also queried whether `#11718` was the right number in the DI section's `EnvGuard` retirement citation — confirmed it is. #11708 introduced the DI seam consolidation; this PR is the one that retires the env-mutation pattern from `default_store_dir`. --- Written by an agent (Claude Code, claude-opus-4-7).
* refactor(pacquet/config): thread default_store_dir through the EnvVar DI seam Replaces process-environment mutation in the `default_store_dir` tests with the dependency-injection pattern established in pnpm/pacquet#339 and consolidated for the in-tree pacquet subtree in #11708. Tracks pnpm/pacquet#343 — the original issue still applied after pacquet was merged into this repo because the env-mutating tests came with it. The four affected unit tests (`test_default_store_dir_with_pnpm_home_env` and `test_default_store_dir_with_xdg_env` in `defaults::tests`, plus `should_use_pnpm_home_env_var` and `should_use_xdg_data_home_env_var` in `lib::tests`) now drive each branch with per-test unit structs that satisfy `EnvVar`. No `EnvGuard` snapshot, no `unsafe` block, no process-environment write. The `home_dir` and `current_dir` closures call `unreachable!` for branches the early `PNPM_HOME` / `XDG_DATA_HOME` returns short-circuit before consulting them, documenting the precondition the way the style guide's worked example does. `default_store_dir` is now generic over `Sys: EnvVar` and takes the `home_dir` and `current_dir` lookups as `FnOnce` closures, mirroring the shape of `Config::current`. A thin args-less wrapper `default_store_dir_host` wires the production `Host` provider together with `home::home_dir` and `env::current_dir` so the SmartDefault expression on `Config::store_dir` stays short. The `have_default_values` wiring assertion now compares against `default_store_dir::<Host>(home::home_dir, env::current_dir)` instead of the old args-less helper. `npmrc_auth::tests::ignores_non_auth_keys` no longer needs to hold the EnvGuard global lock against the two `Config::new()` snapshots it compares — nothing in the crate mutates `PNPM_HOME` or `XDG_DATA_HOME` anymore, so the env-derived `store_dir` is observed identically by both calls even under nextest's in-process parallelism. `EnvGuard` itself stays in `pacquet-testing-utils` because the proxy cascade and `NPM_CONFIG_WORKSPACE_DIR` tests in `lib::tests`, as well as out-of-crate users in `git-fetcher` and `executor`, still rely on it. Retiring it entirely is out of scope for this issue. --- Written by an agent (Claude Code, claude-opus-4-7). * fix(pacquet/config): add Debug bound on default_store_dir's Error parameter The Windows branch of `default_store_dir` calls `current_dir().expect("current directory is unavailable")`, which requires `Error: Debug` on the `Result<PathBuf, Error>` returned by the `CurrentDir` closure. The original `where` clause didn't declare that bound, so the function compiled on Linux/macOS (where the Windows branch is `#[cfg(windows)]`-gated out) but failed on `Lint and Test (windows-latest)` once the cfg fired. All current callers pass `env::current_dir` (Error = `io::Error`) or pin `Error = std::io::Error` via turbofish on test fakes — both already satisfy `Debug` — so this is a pure type-bound fix with no behaviour or call-site change. --- Written by an agent (Claude Code, claude-opus-4-7). * docs(pacquet): add shared-process-state DI exception to the style guide The "Dependency injection for tests" gating list in `pacquet/CODE_STYLE_GUIDE.md` enumerated four reasons to reach past real fixtures for the DI seam: filesystem error kinds, deterministic time, external-service happy paths, and unreachable-by-design preconditions. It did not cover the case this PR retired from `default_store_dir`: tests that mutate a single per-process slot (env vars, cwd, umask, signal handlers, …) and so race with every other test in the same process. The pre-existing `EnvGuard` workaround restored correctness only by holding a binary-wide mutex around `unsafe { env::set_var(...) }`, which forecloses parallelism inside the affected tests and leaves `unsafe` in the test source. A capability-trait fake keeps the read deterministic and the mutation contained to the test that needs it. Adds that bullet to the style guide between "Deterministic time" and "External-service happy paths," and updates rule 7 in `pacquet/AGENTS.md` (and its `CLAUDE.md` / `GEMINI.md` symlink targets) so the summary mirrors the longer list. --- Written by an agent (Claude Code, claude-opus-4-7). * docs(pacquet): drop unfounded set_current_dir example from DI guidance The previous commit cited `set_current_dir` and "the umask" as illustrative examples in both the style guide and the AGENTS rule 7 summary. Neither is actually used anywhere in the pacquet codebase today — only `env::set_var` is. Speculative examples in a style guide invite contributors to chase patterns that don't exist, so narrow the wording to the one slot we have a concrete example for while keeping the principle generalisable (the bullet still says "any future analogue"). --- Written by an agent (Claude Code, claude-opus-4-7). * docs(pacquet): revert example narrowing on the shared-process-state bullet Reviewer feedback: keep the broader `set_current_dir` / umask / signal-handler / global-allocator examples in the DI gating bullet and rule 7 summary. They're not used in the codebase today, but the analogues are useful as forward-looking illustrations so the next contributor recognises the same shape when it comes up. Restores the wording introduced in 96040d7; reverses the narrowing in d930bed. --- Written by an agent (Claude Code, claude-opus-4-7). * docs(pacquet): document when library code may read process state Adds a "Reading process state" section to CODE_STYLE_GUIDE.md, placed right before "Dependency injection for tests" so the reading order captures the two questions in dependency order: first ask whether you should be reaching for `env::var` / `env::current_dir` at all, then ask how to inject it if the answer is yes. The section pins: 1. Library code should rarely call `std::env::var`, `std::env::var_os`, or `std::env::current_dir` directly. The default fix is a parameter (`&Path`, `&str`, an `Option<String>`, or a `FnOnce` closure) so the caller decides which value to read; `Config::current` and `workspace::find_workspace_dir_from_env_with` already follow this. 2. When the read genuinely needs to happen, route through the DI seam (`Sys: EnvVar`, `Sys::var(name)`). 3. The narrow legitimate direct-call case is computing a `Config` default that has no caller — `default_store_dir`, `default_modules_dir`, `default_virtual_store_dir`. This is the "begrudging" use that pnpm/pacquet#343 + #11718 finally threaded through the DI seam. 4. Other accepted boundary reads are program-entry knobs (`RAYON_NUM_THREADS`, `TRACE`) and the lifecycle-script env snapshot in `crates/executor` / `crates/git-fetcher` that forwards the parent env to spawned children verbatim. The section belongs in CODE_STYLE_GUIDE.md (code-level convention), not CONTRIBUTING.md (PR workflow) or AGENTS.md (agent-specific operating rules); rule 7 in AGENTS.md already cross-links the style guide, so no change there. --- Written by an agent (Claude Code, claude-opus-4-7). * refactor(pacquet/config): inline default_store_dir's SmartDefault wiring Reviewer pointed out that the args-less `default_store_dir_host` wrapper could be inlined into the `#[default(_code = ...)]` expression on `Config::store_dir` — the SmartDefault macro accepts the turbofish form `default_store_dir::<Host, _, _, _>(home::home_dir, env::current_dir)` verbatim, and once the wrapper is gone there's nothing for the indirection to earn. Dropping the wrapper also removes a small motivation for the `Host` import inside `defaults.rs` — adjust the doc comment on `default_store_dir` to cite `crate::Host` by full path instead. The `#[cfg(windows)] Error: std::fmt::Debug` bound that the same reviewer asked about is not feasible: attributes on where-clause predicates are still unstable (rust-lang/rust#115590), so the bound stays unconditional with a comment explaining why. --- Written by an agent (Claude Code, claude-opus-4-7). * docs(pacquet/config): purge stale default_store_dir_host references Commit 49c690c inlined the SmartDefault expression for `Config::store_dir` and dropped the `default_store_dir_host` wrapper, but three comments still pointed at the wrapper or at the old single-type-parameter signature of `default_store_dir`. CodeRabbit and Copilot flagged all three on review: - `crates/config/src/lib.rs`, `have_default_values`: the comment said the SmartDefault resolved "via the thin `default_store_dir_host` wrapper". Now reflects the direct `default_store_dir::<Host, _, _, _>(home::home_dir, env::current_dir)` call. - `crates/config/src/npmrc_auth/tests.rs`, `ignores_non_auth_keys`: the comment cited `default_store_dir::<Host>` with no closure parameters. Now spells out the four-parameter turbofish that matches the current signature. - `CODE_STYLE_GUIDE.md`, "Reading process state": the paragraph recommended an args-less `default_store_dir_host` wrapper as the pattern to follow. Now recommends inlining the production composition at the `SmartDefault` site directly. Copilot also queried whether `#11718` was the right number in the DI section's `EnvGuard` retirement citation — confirmed it is. #11708 introduced the DI seam consolidation; this PR is the one that retires the env-mutation pattern from `default_store_dir`. --- Written by an agent (Claude Code, claude-opus-4-7). --------- Co-authored-by: Claude <noreply@anthropic.com>
Adapts pnpm/pacquet#517 (tracking pnpm/pacquet#339) into the in-tree
pacquet/subtree.Important
#11710 is stacked on top of this PR and must wait for this PR to land first.
Summary
Consolidates the dependency-injection seam used by pacquet's filesystem, environment, clock, and hostname capabilities under one naming convention, and writes the convention up so future ports follow it by default.
Renames
Api→Sys(the "system seam" slot).RealApi→Host(production is unqualified; fakes carry behaviour-based names).FsReadString→FsReadToString, aligning withstd::fs::read_to_stringandmodules-yaml's existing trait.*Apisuffix (FakeApi→FakeHostName,PermissionDeniedApi→PermissionDenied,EmptyReadApi→EmptyRead).Documentation
pacquet/CODE_STYLE_GUIDE.mdcovering the eight principles (single-purpose traits, one composed generic parameter, static methods, associated types for data, traits on the implementor, domain-neutral provider with domain-scoped traits, explicit turbofish in production, capabilities as primitives), with amodules-yamlworked example and a gating rule that puts real fixtures first and reserves DI for branches they cannot reach.pacquet/AGENTS.mdlinking the seam into the "match pnpm" checklist.Scope
Affected crates:
modules-yaml,cmd-shim,config,reporter,package-manager,cli. No behaviour change — every production call site retains the same turbofish shape (::<Host>), and every test fake declares the same bounds it did before.Test plan
cargo check --workspace --testscargo fmt --checkcargo clippyclean for renamed crates (only the pre-existingdefaults/tests.rswarning remains, unrelated to this change)cargo test --lib --testspasses forpacquet-modules-yaml,pacquet-cmd-shim,pacquet-config,pacquet-reporterjust ready(run on CI)Written by an agent (Claude Code, claude-opus-4-7).