$ npx rulesync-cli pull✓ Wrote CLAUDE.md (2 rulesets)# Coding Standards- Always use async/await- Prefer named exports
Rule Writing

CLAUDE.md for pnpm Workspaces

pnpm workspaces use strict node_modules and the workspace: protocol. AI generates npm/yarn patterns that create phantom dependencies and broken installs.

7 min read·May 29, 2025

pnpm strict mode catches phantom dependencies — AI generates code that has them

workspace: protocol, strict isolation, catalog alignment, filtering, and lockfile management

Why pnpm Workspaces Need Strict-Mode Rules

pnpm workspaces have a fundamentally different node_modules structure than npm or yarn. pnpm uses a content-addressable store with symlinks — packages can only access dependencies they explicitly declare. This prevents phantom dependencies (importing a package you did not declare, which works with npm hoisting but breaks in production). AI trained on npm/yarn generates code with phantom dependencies that passes on npm and crashes on pnpm.

The second difference: pnpm uses workspace: protocol for internal package references. AI generates "@repo/ui": "^1.0.0" (tries to resolve from npm registry) instead of "@repo/ui": "workspace:*" (resolves to the local workspace package). This breaks builds and installs.

These rules target pnpm 9+ with workspaces. They cover the strict isolation model, workspace protocol, catalog for version alignment, filtering, and lockfile management.

Rule 1: workspace: Protocol for All Internal Packages

The rule: 'Reference internal workspace packages with workspace: protocol: "@repo/ui": "workspace:*" (any version, resolves to local), "@repo/config": "workspace:^" (latest compatible, resolves to local). Never use version numbers for internal packages — they try to resolve from the npm registry. pnpm-workspace.yaml defines which directories contain workspace packages: packages: ["apps/*", "packages/*"].'

For publishing: 'When publishing, pnpm replaces workspace:* with the actual version number. workspace:^ becomes ^actual.version. This means internal development uses local resolution, but published packages have proper version dependencies. Never manually set version numbers for internal references.'

AI generates "@repo/shared": "^1.0.0" or "@repo/shared": "file:../shared" — both wrong. workspace:* is the correct protocol: resolved locally during development, converted to real versions during publishing.

  • workspace:* for any version — workspace:^ for compatible — workspace:~ for patch
  • pnpm-workspace.yaml: packages: ['apps/*', 'packages/*']
  • Never version numbers for internal: '1.0.0' tries npm registry
  • Never file: protocol — workspace: is the correct local resolution
  • Publishing converts workspace:* to actual version automatically
⚠️ workspace:* Not ^1.0.0

AI generates '@repo/ui': '^1.0.0' — pnpm tries the npm registry and fails. workspace:* resolves to the local package. This is the single most common pnpm workspace error AI generates.

Rule 2: Strict Node_Modules Isolation

The rule: 'pnpm strict mode (default) means packages can only import what they declare in their package.json. If package A depends on lodash but package B does not, B cannot import lodash — even though lodash exists in the store. This catches missing dependencies before production. Never set shamefully-hoist=true (it recreates npm flat hoisting, defeating pnpm strict mode).'

For the benefit: 'Strict isolation prevents: phantom dependencies (importing undeclared packages that happen to be hoisted), version conflicts (two packages using different versions of the same dep — pnpm supports this natively), and production crashes (a dependency removed by another package breaks yours). If your code imports X, X must be in your package.json.'

AI generates code that imports packages not declared in the local package.json — it works with npm (hoisting makes everything available) and crashes with pnpm (strict isolation). Run pnpm install after AI generates code — if it fails to resolve, the dependency is missing from package.json.

💡 Strict = Production-Safe

pnpm strict mode prevents phantom dependencies: importing packages not in your package.json. npm hoisting hides these — they work locally, crash in production. Strict mode catches them at install time. Never disable with shamefully-hoist.

Rule 3: Catalog for Version Alignment

The rule: 'Use pnpm catalog (9.5+) for version alignment across the workspace: pnpm catalog set react 19.0.0, then in package.json: "react": "catalog:". All packages that use catalog: get the same version — one command to update React across the entire monorepo. Use for: framework versions, shared dependencies, and any package that should be the same version everywhere.'

For without catalog: 'Before catalog, use syncpack or manypkg for version alignment. Or define versions in a root .npmrc: public-hoist-pattern[]=react. But catalog is the native pnpm solution — type-safe, workspace-aware, and integrated into pnpm install/update.'

AI hardcodes version numbers in every package.json — "react": "19.0.0" in 10 files. Updating means editing 10 files. catalog: centralizes versions: one command updates all packages. One source of truth for version alignment.

  • pnpm catalog set react 19.0.0 — centralized version definition
  • "react": "catalog:" in package.json — resolves to catalog version
  • One command updates all packages — no grepping version strings
  • Use for: frameworks, shared deps, anything that must align across packages
  • pnpm 9.5+ feature — the native version alignment solution
catalog: = One Source

catalog: centralizes version numbers: pnpm catalog set react 19.0.0, then 'react': 'catalog:' in every package.json. One command updates React across the entire monorepo. No grepping, no drift, no mismatches.

Rule 4: Filtering and Workspace Commands

The rule: 'Use pnpm filtering for targeted commands: pnpm --filter @repo/web build (one package), pnpm --filter @repo/web... build (package + its dependencies), pnpm --filter ...@repo/ui build (package + its dependents), pnpm -r build (all packages). Use filtering in CI to build only what you need — not the entire monorepo.'

For common commands: 'pnpm add lodash --filter @repo/web (add to one package). pnpm add -Dw typescript (add dev dep to root workspace). pnpm -r exec -- rm -rf dist (run in all packages). pnpm -r --parallel run dev (parallel dev servers). Use --filter with build systems (Turborepo, Nx) for even smarter filtering.'

For scripts: 'Define workspace-level scripts in root package.json: "build": "pnpm -r build", "test": "pnpm -r test", "dev": "pnpm --filter @repo/web dev". Use turbo run or nx run if you need dependency-aware task scheduling — pnpm -r runs tasks in topological order but without caching.'

Rule 5: Lockfile and Reproducibility

The rule: 'Commit pnpm-lock.yaml to git — it ensures reproducible installs. Use pnpm install --frozen-lockfile in CI (fails if lockfile is outdated). Never use pnpm install without --frozen-lockfile in CI — it can modify the lockfile, creating inconsistency. Update lockfile locally: pnpm install (updates lockfile), then commit the changes.'

For .npmrc: 'Configure pnpm behavior in .npmrc: strict-peer-dependencies=true (fail on peer dep mismatches), auto-install-peers=true (install peer deps automatically), link-workspace-packages=true (resolve workspace packages by default). Commit .npmrc — it affects install behavior for the entire team.'

For node version: 'Pin the Node.js version: engines: { "node": ">=22" } in root package.json. Use .nvmrc or .node-version for version managers. pnpm respects engines — it warns or fails on wrong Node version. Pin pnpm version too: packageManager: "pnpm@9.15.0" in root package.json (corepack enforces it).'

  • Commit pnpm-lock.yaml — --frozen-lockfile in CI — never update in CI
  • .npmrc: strict-peer-dependencies, auto-install-peers, link-workspace-packages
  • engines for Node version — packageManager for pnpm version (corepack)
  • .nvmrc or .node-version for version managers — pinned, consistent
  • pnpm install locally updates lock — commit lock — CI uses --frozen-lockfile

Complete pnpm Workspaces Rules Template

Consolidated rules for pnpm workspace projects.

  • workspace: protocol for all internal packages — never version numbers or file:
  • pnpm-workspace.yaml defines workspace packages — apps/* and packages/*
  • Strict isolation: import only declared deps — never shamefully-hoist=true
  • catalog: for version alignment — one command updates all packages
  • --filter for targeted commands — @repo/web... for package + deps
  • pnpm-lock.yaml committed — --frozen-lockfile in CI — never modify in CI
  • .npmrc: strict-peer-dependencies, auto-install-peers — committed to git
  • packageManager field for pnpm version — engines for Node version — corepack