Three papercuts that together make a lint cost a full pre-push sweep.
`test-affected` took its diff against the local `main`, which is only as
current as the last time someone checked it out — and `main` usually lives
in another worktree here, so it lags. Every commit it lagged by read as a
change of the current branch: crates someone else touched got tested, and
a file every crate compiles against, landing upstream, made the whole run
refuse to scope and send you to `just ready`. Six commits of lag was
enough to attribute an unrelated `Cargo.lock` to a branch that had not
touched it. A `--base` that names a branch now diffs against that
branch's remote-tracking ref. A tag, a revision expression, a qualified
ref and `HEAD` are taken as given, since each already names one commit.
The pre-commit hook formats the Rust files being committed, with the
pinned rustfmt. Formatting is the one Rust check that needs no compile, so
it is the one that can run per commit; clippy and dylint stay in pre-push
where their cost is paid once. A file with unstaged changes as well is
left alone and named, since formatting the working tree and staging the
result would commit the part of it the author held back. The pathnames are
handled in Node, one argument per file, so a path holding a space, a glob
character, or a leading `-` reaches rustfmt intact, and only a regular
file is formatted: rustfmt writes through a symlink.
Both lint failures in pre-push named the command that reports the
findings rather than the one that applies them. `just fix` and the new
`just dylint-fix` fix most of what either finds, which is the difference
between one more sweep and several.
`pnpm/CONTRIBUTING.md` required `just ready` before every commit, explicitly
including documentation and comment edits: a full `cargo nextest run` over
~11,500 tests for changes that cannot break them. The root guide's "never run
all tests" rule was scoped to the TypeScript sections, so it read as not
applying to Rust.
The checks now match what a change can break. Formatter, typos, and
workspace-wide check and lint before every commit, since those are cheap and
catch the cross-crate breakage a scoped selection hides, plus the tests for
what the diff affects. The full local run is reserved for changes whose blast
radius cannot be named, because CI already runs the suite on three platforms.
`just test-affected` resolves changed files to packages through `cargo
metadata` and runs them through `run-rust-tests.mjs`, expanding any `pnpr-*`
selection so feature unification does not silently skip backend tests, and
refusing to guess when a change reaches files every crate compiles against.
Selection is crate-level rather than `rdeps()`-based: `pnpm-cli` holds a third
of the workspace's tests and sits downstream of nearly everything, so `rdeps()`
selects 84% or more of the suite for any core crate. Restructuring that target
is tracked in pnpm/pnpm#14984.
Crate-level selection leaves the dependents unrun, so the new `smoke` profile
runs in their place: one end-to-end test per area of CLI behavior, triggered by
the dependent set rather than added to every run. Membership is by behavior
area rather than code coverage, since nearly every end-to-end test walks the
same install path. An exact-name filterset fails open, so a test checks every
entry against the suite sources.
The `testing-changes` skill carries the selection cookbook and the gotchas that
make a scoped run lie.
Related to pnpm/pnpm#14984.
* docs: fix the setup steps that no longer work
Three steps in the setup instructions fail as written when followed on a fresh
clone.
`pnpm add ./pnpm/dev -g` points at a path that no longer exists; the dev
package moved to `pnpm11/pnpm/dev` along with the rest of the TypeScript CLI.
This one fails quietly rather than loudly: pnpm accepts the nonexistent path,
writes a dangling `link:` into the global store, and the missing `pd` only
shows up a step later. Removing the bad entry then fails too, since `pnpm
remove` reads the manifest it points at.
`pnpm run test-main` does not exist. `test-all` is its successor.
`just init` ends by building `cargo-fixit` from source, which needs OpenSSL's
development headers through `openssl-sys`. Nothing mentions them, and the
failure arrives as a hundred lines of `cargo:rustc-check-cfg` output ending in
`failed to run custom build command for openssl-sys`. The Fedora
build-essentials list further up does not cover it, and is about node-gyp for
the e2e tests rather than the Rust toolchain, so the note belongs here instead.
It also covers the case where OpenSSL exists but not where `openssl-sys` looks
— a Homebrew install, or an image-based distribution with no development
packages — which `OPENSSL_DIR` solves.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* docs: address review on the setup fixes
Three corrections, all of them fair.
The test line recommended running the whole suite, which CLAUDE.md and
AGENTS.md both tell contributors not to do — "Never run all tests in the
repository as it takes a lot of time" — and `test-all` does not cover the Rust
or `pnpr` suites anyway, so calling it "all the tests" overstated it. It now
leads with the targeted forms and mentions the full TypeScript suite as the
exception.
`just init` does not fail on its last step. The justfile installs cargo-fixit
second of three and runs the rustfmt install after it, so an openssl-sys
failure stops the recipe before that last step rather than during it.
And OPENSSL_DIR only tells openssl-sys where an installation is; it cannot
stand in for missing headers and libraries. The previous wording offered it for
"an image-based distribution with no development packages", where it would not
have helped. It now asks for a complete development installation first, with
OPENSSL_DIR for the case where one exists somewhere openssl-sys does not look.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Pin pnpm's rustfmt fork and use a cached wrapper for local formatting,
CI, pre-push checks, and editor integration. The formatter has its own
dated nightly runtime while the workspace keeps its existing compiler.
Choose chain layout by call count and argument complexity, with a
40-column allowance for short expressions. Attach the first method when
the complete first line would otherwise end within the continuation
indentation. Preserve simple receivers,
cap intermediate leading accesses at column 80 and final accesses at
100, and separate fields and
await after wrapped methods. Apply the same rules in conditions.
Keep Max heuristics and compact struct literals, with an independent
35-column destructuring limit. Reformat pnpm and pnpr, extract only the
helpers and test modules needed for existing size limits, and fix macro
commas without relaxing lint rules.
Related to pnpm/pnpm#14562 and pnpm/pnpm#14862.
Commit the pnpm-managed Cargo source block. The block is a function of
Cargo.lock, so an install regenerates it byte for byte and the tracked
`.cargo/config.toml` stops reporting as modified after every install. A new
Rust CI step fails if the committed block and the lockfile drift apart.
Cargo now resolves every crate through `.pnpm/crates` and falls back to its
own registry nowhere, so each job that runs cargo needs an install behind it.
Eight did not have one: both cargo-unused jobs, the micro-benchmark, the
integrated benchmark's build and executor jobs, and the three release builds.
The new `install-crates` action gives them one and narrows the JavaScript half
with `--filter pacquet`, which a filter does not do to the Cargo half.
The release builds go through `cross`, which mounts the checkout at
`/project`. pnpm links each crate into `.pnpm/crates` with a relative symlink,
so a store outside the checkout stops resolving under that mount. Those jobs
install into a store inside the checkout instead, on Linux, the one host where
cross containerizes the build at all.
The two workflows that commit no longer discard the block. It is tracked
content now, and a run that changes it should carry the change.
Claude-Session: https://claude.ai/code/session_01Gg6uVUzLw1MniCLC81TQjP
Claude-Session: https://claude.ai/code/session_01HwJS1pAz9HHQpJWEJAiaUu
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* chore: install the Rust dependencies with pnpm
Turn on `cargo.enabled` so `pnpm install` installs the crates `Cargo.lock`
pins alongside the JavaScript dependencies. pnpm links the registry crates
into `.pnpm/crates/crates-io` and the git-sourced ones into
`.pnpm/crates/git`, then writes a source replacement block into
`.cargo/config.toml` that points Cargo at both.
The `node-semver` fork stays patched in. pnpm installs git-sourced crates
since pnpm/pnpm#14694, which shipped in the 12.4.1 the repository pins, so
enabling this no longer costs the fork's `<=` range and `Ord for Bound`
fixes.
Document the workflow, including the two things a contributor hits first:
`pnpm install` shells out to `cargo`, so it fails when `cargo` is off
`PATH`, and the generated block leaves the tracked `.cargo/config.toml`
modified after every install.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SDxJNVEeyUBb4pcepEpGcj
* ci: initialize Rust before pnpm installs dependencies
Every `pnpm install` now shells out to `cargo metadata`, and there is no
way to opt one out: `cargo.enabled` is read from `pnpm-workspace.yaml`
only, no CLI flag or `PNPM_CONFIG_*` variable overrides it, and `--filter`
does not narrow the Cargo half. So each job whose install runs gets the
pinned toolchain first. `pnpm/setup` installs unless told not to,
`pnpm/update` runs its own install, and `pnpm pipeline` installs before it
runs anything.
The step has to precede the install rather than follow it. The rustup
action ends with `git restore .`, which would otherwise wipe the source
replacement block back out of `.cargo/config.toml`.
Keep that block out of the two workflows that commit. It points at the
gitignored `.pnpm/crates`, so a commit carrying it would break every
Rust-only job and every checkout that has not installed. Both workflows
also decide whether to commit at all by reading `git status --porcelain`,
which the block would make non-empty on every run. Each discards it right
before that check: the release PR in a step of its own, the lockfile
update through the `post-update` command pnpm/update runs between its
install and its commit.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SDxJNVEeyUBb4pcepEpGcj
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* docs: add changeset style rules
Release blog posts are assembled from changesets, and the pnpm 12.0 post
was called hard to read in https://github.com/orgs/pnpm/discussions/14399.
The patterns that made it read as generated (em dashes, "instead of"
tails, colon-joined lists of internals, reasoning asides) come from the
changesets themselves. Of the last 150 changesets, 14 have a contrast
tail, 9 a colon-joined list, and 7 an em dash.
Add a "Changeset style" section to AGENTS.md, adapted for release notes
from the unslop skill the discussion suggests, with two before/after
examples taken from real changesets. Point to it from CONTRIBUTING.md and
add it to the REVIEW_GUIDE.md changeset checks so reviewers enforce it.
AGENTS.md is used rather than a .claude skill because .claude is
gitignored and AGENTS.md is read by every agent.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XtDoGVb7JYAkfJFqQnDRiz
* docs: add language to changeset style example fences
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XtDoGVb7JYAkfJFqQnDRiz
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
The repository's scripts invoke pnpm through the pn and pnx aliases,
which the official installation methods create but Corepack does not,
so a Corepack-managed pnpm fails with errors like pn: Permission denied.
Related to https://github.com/pnpm/pnpm/issues/12448
Switch the fix target from `cargo clippy --fix` to `cargo fixit --clippy`
and pin `cargo-fixit@0.1.13` as a dev prerequisite. `cargo fixit` is faster
than `cargo clippy --fix` on repeated runs because it skips the full re-check
compile between fix rounds, so iterating on a lint cleanup doesn't rebuild
the workspace each pass. No fallback to `cargo clippy --fix`.
- justfile: add a `fix` recipe running
`cargo fixit --clippy --workspace --all-targets --allow-dirty --allow-staged`
- justfile: install `cargo-fixit@0.1.13` from source (`cargo install ... --locked`)
in `just init`; cargo-fixit has no prebuilt binstall binaries
- package.json: `fix:rust` now delegates to `just fix` (single source of truth,
drops the `cargo clippy --fix` invocation)
- CONTRIBUTING: document cargo-fixit in the `just init` tool list and the
`just fix` workflow
Strengthen the duplicate-PR guidance to point at the issue's
automatically linked PRs, add an AI-assisted contributions section
(agents are welcome, but contributors own the output — check linked
PRs, understand the change, run the tests, keep the diff focused,
disclose agent authorship with the standard footer), and document that
human review starts only after CodeRabbit approves and CI is green.
CodeRabbit is the single required AI approval — Qodo ignores
Dependabot/Renovate PRs, so a two-reviewer gate could never be
satisfied for dependency updates; the PR template is aligned
accordingly.
Spelling was the only Rust CI gate with no local counterpart in the
pre-push hook, so typos surfaced for the first time in CI. Add a typos
block mirroring the "Rust CI / Spell Check" job (same dirs), with the
same skip-if-not-installed behavior as the dylint and taplo checks —
`just init` already installs typos-cli.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Pure directory move plus path fixups: the Rust port ships as pnpm v12,
so the source tree now lives at pnpm/ (alongside pnpm11/, the frozen
TypeScript line). No identifiers change in this pass — crate names
(pacquet-*), the pacquet bin, PACQUET_VERSION, the @pacquet/* npm
package names v11's runPacquet spawns, the .pacquet virtual-store dir,
the benchmark harness's clone dir, and the pacquet-*.yml workflow
filenames (npm trusted publishing is bound to them) all stay for a
follow-up.
Also removes the root /pnpm/ .gitignore entry (build detritus in the
pre-pnpm11 package location): pnpm/ is real source now and must not be
ignored. Developers with a stale generated pnpm/ dir should delete it
before checking out this change.
* docs(contributing): document Rust toolchain and git-hook tooling
Rust is now the primary language in this repository, but the root
CONTRIBUTING.md only covered the TypeScript setup. Add a "Rust toolchain
and git hooks" section under "Setting Up the Environment" that covers
rustup and the pinned toolchain, just, the just init tools, and the
dylint tools.
Two things that are easy to get wrong and cost real debugging time:
- cargo-dylint and dylint-link must be installed from source, not with
cargo binstall. The prebuilt binaries reference the dylint_driver
crate at the path where they were built, so building the per-toolchain
driver fails locally with an error pointing at a nonexistent
.../dylint/driver directory.
- ~/.cargo/bin must be on PATH (ahead of any system Rust in /usr/bin),
because the pre-push hook locates its tools through PATH and silently
skips a Rust check when the tool is missing rather than failing, so a
push that looks clean locally can still fail format, doc, or dylint in
CI.
Point pacquet/CONTRIBUTING.md at the new root section instead of
repeating the tool list, and correct its cargo binstall advice for the
dylint tools.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs: correct dylint install hint in justfile and pre-push hook
The `just dylint` recipe comment and the pre-push hook's skip message
both told contributors to install cargo-dylint via `cargo binstall`,
which produces a prebuilt binary that fails to build the per-toolchain
driver locally. Point both at `cargo install` from source, matching the
CONTRIBUTING.md guidance.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@zkochan/git-wt 0.0.3 looks for an executable .git-wt/pr-hook in the
worktree before falling back to ~/.config/git-wt/pr-hook. Shipping the
hook in-repo gives every contributor with Claude Code installed an
auto-launched PR review via `wt <pr-number>`. The hook silently no-ops
when `claude` isn't on PATH so contributors who don't use it aren't
affected.
* chore: use @zkochan/git-wt package for worktree creation
Replace the in-repo `worktree:new` script and `shell/wt.*` helpers with
the published `@zkochan/git-wt` package. Contributors now install it
globally (`pnpm add -g @zkochan/git-wt`) and enable the `wt` shell
function via `git-wt init <shell>`, which also makes `git wt <branch>`
available as a native git subcommand.
* chore: remove shell/cleanup-worktrees.sh
Its functionality is now available as `git-wt cleanup` in the
@zkochan/git-wt package, which contributors are already being directed
to install in CONTRIBUTING.md.
* docs: give copy-paste install commands for the wt shell function
Previously CONTRIBUTING.md said "add this line to your config" and showed
the snippet, making contributors open the rc file themselves. Replace with
a one-liner per shell that appends to the rc file and activates `wt` in the
current session in one go.
Adds a `pnpm worktree:new <branch>` script that creates a git worktree
as a sibling directory of the repo root, with branch slashes replaced by
dashes (e.g. `feat/foo` → `../feat-foo`). Shell helpers in `shell/wt.fish`
and `shell/wt.sh` wrap the script to also `cd` into the new worktree.
Documents setup and usage in CONTRIBUTING.md.
* Revert "docs: update CONTRIBUTING.md for Git LFS (#8647)"
This reverts commit ccf5fbc1b2.
* Revert "chore: use Git LFS for pnpm development (#8509)"
This reverts commit 5fea44486e.