Skip to content

refactor(pacquet): optional DI pattern + documentation#11708

Merged
KSXGitHub merged 10 commits into
mainfrom
claude/pacquet-dependency-injection-fEZzx
May 18, 2026
Merged

refactor(pacquet): optional DI pattern + documentation#11708
KSXGitHub merged 10 commits into
mainfrom
claude/pacquet-dependency-injection-fEZzx

Conversation

@KSXGitHub

@KSXGitHub KSXGitHub commented May 18, 2026

Copy link
Copy Markdown
Contributor

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

  • Generic parameter ApiSys (the "system seam" slot).
  • Production provider RealApiHost (production is unqualified; fakes carry behaviour-based names).
  • Trait FsReadStringFsReadToString, aligning with std::fs::read_to_string and modules-yaml's existing trait.
  • Test fakes drop the *Api suffix (FakeApiFakeHostName, PermissionDeniedApiPermissionDenied, EmptyReadApiEmptyRead).

Documentation

  • New Dependency injection for tests section in pacquet/CODE_STYLE_GUIDE.md covering 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 a modules-yaml worked example and a gating rule that puts real fixtures first and reserves DI for branches they cannot reach.
  • New rule 7 in pacquet/AGENTS.md linking 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 --tests
  • cargo fmt --check
  • cargo clippy clean for renamed crates (only the pre-existing defaults/tests.rs warning remains, unrelated to this change)
  • cargo test --lib --tests passes for pacquet-modules-yaml, pacquet-cmd-shim, pacquet-config, pacquet-reporter
  • Full just ready (run on CI)

Written by an agent (Claude Code, claude-opus-4-7).

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.
Copilot AI review requested due to automatic review settings May 18, 2026 00:00
@KSXGitHub KSXGitHub requested a review from zkochan as a code owner May 18, 2026 00:00
@KSXGitHub KSXGitHub marked this pull request as draft May 18, 2026 00:00
@coderabbitai

coderabbitai Bot commented May 18, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

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

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

<review_stack_artifact>

</review_stack_artifact>

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The pull request title clearly summarizes the main change: refactoring the dependency-injection (DI) pattern across multiple crates and adding documentation for the new convention.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/pacquet-dependency-injection-fEZzx

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

❤️ Share

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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 ApiSys and production provider RealApiHost across crates and tests.
  • Aligns filesystem capability trait naming FsReadStringFsReadToString and updates all call sites/bounds.
  • Adds/links documentation for the DI pattern in CODE_STYLE_GUIDE.md and AGENTS.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 RealApiHost 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 ApiSys, provider RealApiHost, and updates trait bounds/calls.
pacquet/crates/package-manager/src/install/tests.rs Switches modules-yaml provider turbofish from RealApiHost.
pacquet/crates/package-manager/src/install.rs Switches modules-yaml provider turbofish from RealApiHost.
pacquet/crates/package-manager/src/install_without_lockfile.rs Switches cmd-shim provider turbofish from RealApiHost.
pacquet/crates/package-manager/src/install_frozen_lockfile.rs Switches modules-yaml provider turbofish from RealApiHost.
pacquet/crates/modules-yaml/tests/real_fs.rs Switches modules-yaml provider turbofish from RealApiHost.
pacquet/crates/modules-yaml/tests/index.rs Switches modules-yaml provider turbofish from RealApiHost.
pacquet/crates/modules-yaml/src/lib.rs Renames production provider RealApiHost and DI seam ApiSys.
pacquet/crates/config/src/npmrc_auth.rs Renames DI seam ApiSys in .npmrc parsing/proxy cascade helpers.
pacquet/crates/config/src/lib.rs Re-exports Host and renames DI seam ApiSys in Config::current.
pacquet/crates/config/src/env_replace.rs Renames DI seam ApiSys and updates docs/tests helpers.
pacquet/crates/config/src/api.rs Renames provider RealApiHost and updates module docs.
pacquet/crates/cmd-shim/src/shim/tests.rs Switches provider RealApiHost and renames fakes away from *Api.
pacquet/crates/cmd-shim/src/shim.rs Renames DI seam ApiSys in shebang/runtime probing helpers.
pacquet/crates/cmd-shim/src/link_bins/tests.rs Updates provider RealApiHost and trait FsReadStringFsReadToString.
pacquet/crates/cmd-shim/src/link_bins.rs Renames DI seam ApiSys and trait FsReadStringFsReadToString.
pacquet/crates/cmd-shim/src/capabilities.rs Renames provider RealApiHost and capability trait FsReadStringFsReadToString.
pacquet/crates/cmd-shim/src/bin_resolver/tests.rs Switches provider turbofish from RealApiHost.
pacquet/crates/cmd-shim/src/bin_resolver.rs Renames DI seam ApiSys 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 RealApiHost 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.

Comment thread pacquet/CODE_STYLE_GUIDE.md Outdated
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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 247d70b and 1a8bb37.

📒 Files selected for processing (26)
  • pacquet/AGENTS.md
  • pacquet/CODE_STYLE_GUIDE.md
  • pacquet/crates/cli/src/cli_args.rs
  • pacquet/crates/cli/tests/install.rs
  • pacquet/crates/cmd-shim/src/bin_resolver.rs
  • pacquet/crates/cmd-shim/src/bin_resolver/tests.rs
  • pacquet/crates/cmd-shim/src/capabilities.rs
  • pacquet/crates/cmd-shim/src/link_bins.rs
  • pacquet/crates/cmd-shim/src/link_bins/tests.rs
  • pacquet/crates/cmd-shim/src/shim.rs
  • pacquet/crates/cmd-shim/src/shim/tests.rs
  • pacquet/crates/config/src/api.rs
  • pacquet/crates/config/src/env_replace.rs
  • pacquet/crates/config/src/lib.rs
  • pacquet/crates/config/src/npmrc_auth.rs
  • pacquet/crates/modules-yaml/src/lib.rs
  • pacquet/crates/modules-yaml/tests/index.rs
  • pacquet/crates/modules-yaml/tests/real_fs.rs
  • pacquet/crates/package-manager/src/install.rs
  • pacquet/crates/package-manager/src/install/tests.rs
  • pacquet/crates/package-manager/src/install_frozen_lockfile.rs
  • pacquet/crates/package-manager/src/install_without_lockfile.rs
  • pacquet/crates/package-manager/src/link_bins.rs
  • pacquet/crates/package-manager/src/link_bins/tests.rs
  • pacquet/crates/reporter/src/lib.rs
  • pacquet/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 through globalLogger/logger.debug(...)/streamParser.write(...), mirror the call site, payload, and ordering so @pnpm/cli.default-reporter parses 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 Host provider, threaded as Sys: <Bounds> through the function signature. Follow the eight principles and modules-yaml worked example documented in CODE_STYLE_GUIDE.md.

Declare a newtype wrapper for branded string types instead of collapsing the brand into plain String or &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/or FromStr. 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> (and From<&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 a from_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_more when 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.rs
  • pacquet/crates/reporter/src/lib.rs
  • pacquet/crates/cli/tests/install.rs
  • pacquet/crates/modules-yaml/tests/index.rs
  • pacquet/crates/config/src/api.rs
  • pacquet/crates/cli/src/cli_args.rs
  • pacquet/crates/reporter/src/tests.rs
  • pacquet/crates/config/src/env_replace.rs
  • pacquet/crates/modules-yaml/tests/real_fs.rs
  • pacquet/crates/package-manager/src/install/tests.rs
  • pacquet/crates/cmd-shim/src/capabilities.rs
  • pacquet/crates/package-manager/src/link_bins/tests.rs
  • pacquet/crates/package-manager/src/install.rs
  • pacquet/crates/package-manager/src/install_frozen_lockfile.rs
  • pacquet/crates/cmd-shim/src/shim.rs
  • pacquet/crates/cmd-shim/src/bin_resolver.rs
  • pacquet/crates/config/src/npmrc_auth.rs
  • pacquet/crates/cmd-shim/src/shim/tests.rs
  • pacquet/crates/modules-yaml/src/lib.rs
  • pacquet/crates/config/src/lib.rs
  • pacquet/crates/package-manager/src/link_bins.rs
  • pacquet/crates/cmd-shim/src/bin_resolver/tests.rs
  • pacquet/crates/cmd-shim/src/link_bins/tests.rs
  • pacquet/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 under crates/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 scalar assert_eq!.

Files:

  • pacquet/crates/cli/tests/install.rs
  • pacquet/crates/modules-yaml/tests/index.rs
  • pacquet/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

Comment thread pacquet/CODE_STYLE_GUIDE.md
@codecov-commenter

codecov-commenter commented May 18, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.63866% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.93%. Comparing base (f46757d) to head (f35b23d).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
pacquet/crates/cmd-shim/src/link_bins.rs 86.20% 4 Missing ⚠️
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.
📢 Have feedback on the report? Share it here.

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

@github-actions

github-actions Bot commented May 18, 2026

Copy link
Copy Markdown
Contributor

Micro-Benchmark Results

Linux

group                          main                                   pr
-----                          ----                                   --
tarball/download_dependency    1.03      6.0±0.11ms   728.6 KB/sec    1.00      5.8±0.02ms   751.2 KB/sec

@github-actions

github-actions Bot commented May 18, 2026

Copy link
Copy Markdown
Contributor

Integrated-Benchmark Report (Linux)

Scenario: Frozen Lockfile

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 2.349 ± 0.086 2.248 2.550 1.02 ± 0.05
pacquet@main 2.296 ± 0.077 2.193 2.478 1.00
pnpm 4.590 ± 0.048 4.506 4.661 2.00 ± 0.07
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)

Command Mean [ms] Min [ms] Max [ms] Relative
pacquet@HEAD 689.7 ± 20.5 667.9 742.5 1.00
pacquet@main 729.5 ± 70.6 665.6 834.7 1.06 ± 0.11
pnpm 2343.8 ± 58.5 2279.0 2467.7 3.40 ± 0.13
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
      ]
    }
  ]
}

claude added 4 commits May 18, 2026 00:19
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.
@KSXGitHub KSXGitHub mentioned this pull request May 18, 2026
3 tasks
@KSXGitHub KSXGitHub marked this pull request as ready for review May 18, 2026 00:54
Copilot AI review requested due to automatic review settings May 18, 2026 00:54

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 26 out of 26 changed files in this pull request and generated 2 comments.

Comment thread pacquet/crates/config/src/api.rs Outdated
Comment on lines 1 to 16
//! 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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment on lines 311 to 314
/// [`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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
pacquet/CODE_STYLE_GUIDE.md (1)

796-809: ⚡ Quick win

Clarify the stateful-fake example with a compilable SystemTime initialization.

Line 798's static FAKE_NOW: SystemTime = /* fixed instant */; is pseudo-code that won't compile—SystemTime has no const constructor 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_static or once_cell::sync::Lazy pattern explicitly, though UNIX_EPOCH is 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

📥 Commits

Reviewing files that changed from the base of the PR and between 1a8bb37 and ac0577d.

📒 Files selected for processing (2)
  • pacquet/AGENTS.md
  • pacquet/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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 26 out of 26 changed files in this pull request and generated 4 comments.

Comment on lines +18 to +19
//! [Dependency injection for tests](../../../CODE_STYLE_GUIDE.md#dependency-injection-for-tests)
//! section of the style guide for the full convention.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread pacquet/crates/config/src/api.rs Outdated
Comment on lines 37 to 42
/// 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);
/// ```

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment on lines +311 to 319
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))
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two reasons I'm leaving this as-is:

  1. The shadowing predates this PR. The inner fn env_pair<Api: EnvVar> was already there before the rename; this PR only retitled ApiSys. Fixing the structure is out of scope for a mechanical rename.

  2. The suggestion as worded doesn't compile. Inner fn items 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 converting env_pair to a closure (which does capture Sys), 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

Comment on lines 741 to 746
/// 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.
///

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
Copilot AI review requested due to automatic review settings May 18, 2026 05:21

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 26 out of 26 changed files in this pull request and generated 1 comment.

Comment thread pacquet/CODE_STYLE_GUIDE.md Outdated
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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
@KSXGitHub KSXGitHub enabled auto-merge (squash) May 18, 2026 05:31
@KSXGitHub KSXGitHub merged commit 02f8138 into main May 18, 2026
28 checks passed
@KSXGitHub KSXGitHub deleted the claude/pacquet-dependency-injection-fEZzx branch May 18, 2026 06:08
KSXGitHub pushed a commit that referenced this pull request May 18, 2026
…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
KSXGitHub pushed a commit that referenced this pull request May 18, 2026
… 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.
KSXGitHub pushed a commit that referenced this pull request May 18, 2026
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).
zkochan pushed a commit that referenced this pull request May 18, 2026
* 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants