Files
claude-skills-reference/docs/skills/engineering/monorepo-navigator.md
Reza Rezvani 2c72babd52 feat: add MkDocs Material docs site with 170 auto-generated skill pages
- mkdocs.yml: Material theme with dark/light mode, search, tabs, sitemap
- scripts/generate-docs.py: auto-generates docs from all SKILL.md files
- docs/index.md: landing page with domain overview and quick install
- docs/getting-started.md: installation guide for Claude Code, Codex, OpenClaw
- docs/skills/: 170 skill pages + 9 domain index pages
- .github/workflows/static.yml: MkDocs build + GitHub Pages deploy
- .gitignore: exclude site/ build output

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 12:16:26 +01:00

16 KiB

title, description
title description
Monorepo Navigator Monorepo Navigator - Claude Code skill from the Engineering - POWERFUL domain.

Monorepo Navigator

Domain: Engineering - POWERFUL | Skill: monorepo-navigator | Source: engineering/monorepo-navigator/SKILL.md


Tier: POWERFUL
Category: Engineering
Domain: Monorepo Architecture / Build Systems


Overview

Navigate, manage, and optimize monorepos. Covers Turborepo, Nx, pnpm workspaces, and Lerna. Enables cross-package impact analysis, selective builds/tests on affected packages only, remote caching, dependency graph visualization, and structured migrations from multi-repo to monorepo. Includes Claude Code configuration for workspace-aware development.


Core Capabilities

  • Cross-package impact analysis — determine which apps break when a shared package changes
  • Selective commands — run tests/builds only for affected packages (not everything)
  • Dependency graph — visualize package relationships as Mermaid diagrams
  • Build optimization — remote caching, incremental builds, parallel execution
  • Migration — step-by-step multi-repo → monorepo with zero history loss
  • Publishing — changesets for versioning, pre-release channels, npm publish workflows
  • Claude Code config — workspace-aware CLAUDE.md with per-package instructions

When to Use

Use when:

  • Multiple packages/apps share code (UI components, utils, types, API clients)
  • Build times are slow because everything rebuilds when anything changes
  • Migrating from multiple repos to a single repo
  • Need to publish packages to npm with coordinated versioning
  • Teams work across multiple packages and need unified tooling

Skip when:

  • Single-app project with no shared packages
  • Team/project boundaries are completely isolated (polyrepo is fine)
  • Shared code is minimal and copy-paste overhead is acceptable

Tool Selection

Tool Best For Key Feature
Turborepo JS/TS monorepos, simple pipeline config Best-in-class remote caching, minimal config
Nx Large enterprises, plugin ecosystem Project graph, code generation, affected commands
pnpm workspaces Workspace protocol, disk efficiency workspace:* for local package refs
Lerna npm publishing, versioning Batch publishing, conventional commits
Changesets Modern versioning (preferred over Lerna) Changelog generation, pre-release channels

Most modern setups: pnpm workspaces + Turborepo + Changesets


Turborepo

turbo.json pipeline config

{
  "$schema": "https://turbo.build/schema.json",
  "globalEnv": ["NODE_ENV", "DATABASE_URL"],
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],    // build deps first (topological order)
      "outputs": [".next/**", "dist/**", "build/**"],
      "env": ["NEXT_PUBLIC_API_URL"]
    },
    "test": {
      "dependsOn": ["^build"],    // need built deps to test
      "outputs": ["coverage/**"],
      "cache": true
    },
    "lint": {
      "outputs": [],
      "cache": true
    },
    "dev": {
      "cache": false,             // never cache dev servers
      "persistent": true          // long-running process
    },
    "type-check": {
      "dependsOn": ["^build"],
      "outputs": []
    }
  }
}

Key commands

# Build everything (respects dependency order)
turbo run build

# Build only affected packages (requires --filter)
turbo run build --filter=...[HEAD^1]   # changed since last commit
turbo run build --filter=...[main]     # changed vs main branch

# Test only affected
turbo run test --filter=...[HEAD^1]

# Run for a specific app and all its dependencies
turbo run build --filter=@myorg/web...

# Run for a specific package only (no dependencies)
turbo run build --filter=@myorg/ui

# Dry-run — see what would run without executing
turbo run build --dry-run

# Enable remote caching (Vercel Remote Cache)
turbo login
turbo link

Remote caching setup

# .turbo/config.json (auto-created by turbo link)
{
  "teamid": "team_xxxx",
  "apiurl": "https://vercel.com"
}

# Self-hosted cache server (open-source alternative)
# Run ducktape/turborepo-remote-cache or Turborepo's official server
TURBO_API=http://your-cache-server.internal \
TURBO_TOKEN=your-token \
TURBO_TEAM=your-team \
turbo run build

Nx

Project graph and affected commands

# Install
npx create-nx-workspace@latest my-monorepo

# Visualize the project graph (opens browser)
nx graph

# Show affected packages for the current branch
nx affected:graph

# Run only affected tests
nx affected --target=test

# Run only affected builds
nx affected --target=build

# Run affected with base/head (for CI)
nx affected --target=test --base=main --head=HEAD

nx.json configuration

{
  "$schema": "./node_modules/nx/schemas/nx-schema.json",
  "targetDefaults": {
    "build": {
      "dependsOn": ["^build"],
      "cache": true
    },
    "test": {
      "cache": true,
      "inputs": ["default", "^production"]
    }
  },
  "namedInputs": {
    "default":    ["{projectRoot}/**/*", "sharedGlobals"],
    "production": ["default", "!{projectRoot}/**/*.spec.ts", "!{projectRoot}/jest.config.*"],
    "sharedGlobals": []
  },
  "parallel": 4,
  "cacheDirectory": "/tmp/nx-cache"
}

pnpm Workspaces

pnpm-workspace.yaml

packages:
  - 'apps/*'
  - 'packages/*'
  - 'tools/*'

workspace:* protocol for local packages

// apps/web/package.json
{
  "name": "@myorg/web",
  "dependencies": {
    "@myorg/ui":     "workspace:*",   // always use local version
    "@myorg/utils":  "workspace:^",   // local, but respect semver on publish
    "@myorg/types":  "workspace:~"
  }
}

Useful pnpm workspace commands

# Install all packages across workspace
pnpm install

# Run script in a specific package
pnpm --filter @myorg/web dev

# Run script in all packages
pnpm --filter "*" build

# Run script in a package and all its dependencies
pnpm --filter @myorg/web... build

# Add a dependency to a specific package
pnpm --filter @myorg/web add react

# Add a shared dev dependency to root
pnpm add -D typescript -w

# List workspace packages
pnpm ls --depth -1 -r

Cross-Package Impact Analysis

When a shared package changes, determine what's affected before you ship.

# Using Turborepo — show affected packages
turbo run build --filter=...[HEAD^1] --dry-run 2>&1 | grep "Tasks to run"

# Using Nx
nx affected:apps --base=main --head=HEAD    # which apps are affected
nx affected:libs --base=main --head=HEAD    # which libs are affected

# Manual analysis with pnpm
# Find all packages that depend on @myorg/utils:
grep -r '"@myorg/utils"' packages/*/package.json apps/*/package.json

# Using jq for structured output
for pkg in packages/*/package.json apps/*/package.json; do
  name=$(jq -r '.name' "$pkg")
  if jq -e '.dependencies["@myorg/utils"] // .devDependencies["@myorg/utils"]' "$pkg" > /dev/null 2>&1; then
    echo "$name depends on @myorg/utils"
  fi
done

Dependency Graph Visualization

Generate a Mermaid diagram from your workspace:

# Generate dependency graph as Mermaid
cat > scripts/gen-dep-graph.js << 'EOF'
const { execSync } = require('child_process');
const fs = require('fs');

// Parse pnpm workspace packages
const packages = JSON.parse(
  execSync('pnpm ls --depth -1 -r --json').toString()
);

let mermaid = 'graph TD\n';
packages.forEach(pkg => {
  const deps = Object.keys(pkg.dependencies || {})
    .filter(d => d.startsWith('@myorg/'));
  deps.forEach(dep => {
    const from = pkg.name.replace('@myorg/', '');
    const to = dep.replace('@myorg/', '');
    mermaid += `  ${from} --> ${to}\n`;
  });
});

fs.writeFileSync('docs/dep-graph.md', '```mermaid\n' + mermaid + '```\n');
console.log('Written to docs/dep-graph.md');
EOF
node scripts/gen-dep-graph.js

Example output:

graph TD
  web --> ui
  web --> utils
  web --> types
  mobile --> ui
  mobile --> utils
  mobile --> types
  admin --> ui
  admin --> utils
  api --> types
  ui --> utils

Claude Code Configuration (Workspace-Aware CLAUDE.md)

Place a root CLAUDE.md + per-package CLAUDE.md files:

# /CLAUDE.md — Root (applies to all packages)

## Monorepo Structure
- apps/web       — Next.js customer-facing app
- apps/admin     — Next.js internal admin
- apps/api       — Express REST API
- packages/ui    — Shared React component library
- packages/utils — Shared utilities (pure functions only)
- packages/types — Shared TypeScript types (no runtime code)

## Build System
- pnpm workspaces + Turborepo
- Always use `pnpm --filter <package>` to scope commands
- Never run `npm install` or `yarn` — pnpm only
- Run `turbo run build --filter=...[HEAD^1]` before committing

## Task Scoping Rules
- When modifying packages/ui: also run tests for apps/web and apps/admin (they depend on it)
- When modifying packages/types: run type-check across ALL packages
- When modifying apps/api: only need to test apps/api

## Package Manager
pnpm — version pinned in packageManager field of root package.json
# /packages/ui/CLAUDE.md — Package-specific

## This Package
Shared React component library. Zero business logic. Pure UI only.

## Rules
- All components must be exported from src/index.ts
- No direct API calls in components — accept data via props
- Every component needs a Storybook story in src/stories/
- Use Tailwind for styling — no CSS modules or styled-components

## Testing
- Component tests: `pnpm --filter @myorg/ui test`
- Visual regression: `pnpm --filter @myorg/ui test:storybook`

## Publishing
- Version bumps via changesets only — never edit package.json version manually
- Run `pnpm changeset` from repo root after changes

Migration: Multi-Repo → Monorepo

# Step 1: Create monorepo scaffold
mkdir my-monorepo && cd my-monorepo
pnpm init
echo "packages:\n  - 'apps/*'\n  - 'packages/*'" > pnpm-workspace.yaml

# Step 2: Move repos with git history preserved
mkdir -p apps packages

# For each existing repo:
git clone https://github.com/myorg/web-app
cd web-app
git filter-repo --to-subdirectory-filter apps/web  # rewrites history into subdir
cd ..
git remote add web-app ./web-app
git fetch web-app --tags
git merge web-app/main --allow-unrelated-histories

# Step 3: Update package names to scoped
# In each package.json, change "name": "web" to "name": "@myorg/web"

# Step 4: Replace cross-repo npm deps with workspace:*
# apps/web/package.json: "@myorg/ui": "1.2.3" → "@myorg/ui": "workspace:*"

# Step 5: Add shared configs to root
cp apps/web/.eslintrc.js .eslintrc.base.js
# Update each package's config to extend root:
# { "extends": ["../../.eslintrc.base.js"] }

# Step 6: Add Turborepo
pnpm add -D turbo -w
# Create turbo.json (see above)

# Step 7: Unified CI (see CI section below)
# Step 8: Test everything
turbo run build test lint

CI Patterns

GitHub Actions — Affected Only

# .github/workflows/ci.yml
name: CI

on:
  push:
    branches: [main]
  pull_request:

jobs:
  affected:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0          # full history needed for affected detection

      - uses: pnpm/action-setup@v3
        with:
          version: 9

      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: pnpm

      - run: pnpm install --frozen-lockfile

      # Turborepo remote cache
      - uses: actions/cache@v4
        with:
          path: .turbo
          key: ${{ runner.os }}-turbo-${{ github.sha }}
          restore-keys: ${{ runner.os }}-turbo-

      # Only test/build affected packages
      - name: Build affected
        run: turbo run build --filter=...[origin/main]
        env:
          TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
          TURBO_TEAM: ${{ vars.TURBO_TEAM }}

      - name: Test affected
        run: turbo run test --filter=...[origin/main]

      - name: Lint affected
        run: turbo run lint --filter=...[origin/main]

GitLab CI — Parallel Stages

# .gitlab-ci.yml
stages: [install, build, test, publish]

variables:
  PNPM_CACHE_FOLDER: .pnpm-store

cache:
  key: pnpm-$CI_COMMIT_REF_SLUG
  paths: [.pnpm-store/, .turbo/]

install:
  stage: install
  script:
    - pnpm install --frozen-lockfile
  artifacts:
    paths: [node_modules/, packages/*/node_modules/, apps/*/node_modules/]
    expire_in: 1h

build:affected:
  stage: build
  needs: [install]
  script:
    - turbo run build --filter=...[origin/main]
  artifacts:
    paths: [apps/*/dist/, apps/*/.next/, packages/*/dist/]

test:affected:
  stage: test
  needs: [build:affected]
  script:
    - turbo run test --filter=...[origin/main]
  coverage: '/Statements\s*:\s*(\d+\.?\d*)%/'
  artifacts:
    reports:
      coverage_report:
        coverage_format: cobertura
        path: "**/coverage/cobertura-coverage.xml"

Publishing with Changesets

# Install changesets
pnpm add -D @changesets/cli -w
pnpm changeset init

# After making changes, create a changeset
pnpm changeset
# Interactive: select packages, choose semver bump, write changelog entry

# In CI — version packages + update changelogs
pnpm changeset version

# Publish all changed packages
pnpm changeset publish

# Pre-release channel (for alpha/beta)
pnpm changeset pre enter beta
pnpm changeset
pnpm changeset version   # produces 1.2.0-beta.0
pnpm changeset publish --tag beta
pnpm changeset pre exit  # back to stable releases

Automated publish workflow (GitHub Actions)

# .github/workflows/release.yml
name: Release

on:
  push:
    branches: [main]

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v3
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          registry-url: https://registry.npmjs.org

      - run: pnpm install --frozen-lockfile

      - name: Create Release PR or Publish
        uses: changesets/action@v1
        with:
          publish: pnpm changeset publish
          version: pnpm changeset version
          commit: "chore: release packages"
          title: "chore: release packages"
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

Common Pitfalls

Pitfall Fix
Running turbo run build without --filter on every PR Always use --filter=...[origin/main] in CI
workspace:* refs cause publish failures Use pnpm changeset publish — it replaces workspace:* with real versions automatically
All packages rebuild when unrelated file changes Tune inputs in turbo.json to exclude docs, config files from cache keys
Shared tsconfig causes one package to break all type-checks Use extends properly — each package extends root but overrides rootDir / outDir
git history lost during migration Use git filter-repo --to-subdirectory-filter before merging — never move files manually
Remote cache not working in CI Check TURBO_TOKEN and TURBO_TEAM env vars; verify with turbo run build --summarize
CLAUDE.md too generic — Claude modifies wrong package Add explicit "When working on X, only touch files in apps/X" rules per package CLAUDE.md

Best Practices

  1. Root CLAUDE.md defines the map — document every package, its purpose, and dependency rules
  2. Per-package CLAUDE.md defines the rules — what's allowed, what's forbidden, testing commands
  3. Always scope commands with --filter — running everything on every change defeats the purpose
  4. Remote cache is not optional — without it, monorepo CI is slower than multi-repo CI
  5. Changesets over manual versioning — never hand-edit package.json versions in a monorepo
  6. Shared configs in root, extended in packages — tsconfig.base.json, .eslintrc.base.js, jest.base.config.js
  7. Impact analysis before merging shared package changes — run affected check, communicate blast radius
  8. Keep packages/types as pure TypeScript — no runtime code, no dependencies, fast to build and type-check