map-project-monorepo
SKIPPING DISCOVERY = INCOMPLETE PER-PACKAGE DOCS = CLAUDE SEARCHING BLINDLY
This is not optional. Each sub-directory CLAUDE.md must be self-contained. Claude lazy-loads these files — they ONLY load when Claude touches files in that directory. If the CLAUDE.md is incomplete, Claude has NO context for that package.
Update CLAUDE.md for Monorepo
MANDATORY FIRST RESPONSE PROTOCOL
Before writing ANY documentation:
- ☐ Discover all
packages/*/andapps/*/directories - ☐ For each, count exports and identify key files
- ☐ Map inter-package dependencies
- ☐ Identify which packages have tests
- ☐ Announce: "Generating CLAUDE.md for X packages + Y apps, targeting 10/10"
Writing per-package docs without discovery = guaranteed gaps. Phase 1 is NON-NEGOTIABLE.
Overview
In a monorepo, Claude Code lazy-loads subdirectory CLAUDE.md files — they only load when Claude touches files in that directory. Sibling packages never see each other's CLAUDE.md. This means each per-package CLAUDE.md must be self-contained: everything Claude needs to work effectively in that package, right there.
This skill generates a focused CLAUDE.md for every packages/*/ and apps/*/ directory, then simplifies the root CLAUDE.md and reference files by pushing per-package detail down to where Claude actually needs it.
Core principle: Detail lives where it's used. Root stays lean. Packages are self-contained.
Quality target: 10/10 — Claude should implement features correctly on first attempt in ANY package.
How Claude Code Loads Subdirectory CLAUDE.md
Understanding the loading mechanism is critical:
| Behavior | Detail |
|---|---|
| Lazy loading | packages/foo/CLAUDE.md loads ONLY when Claude reads/writes files under packages/foo/ |
| No cross-loading | Working in packages/foo/ does NOT load packages/bar/CLAUDE.md |
| Root always loads | Root CLAUDE.md loads at startup for every session |
| @imports work | Per-package CLAUDE.md can use @ imports for shared reference files |
| Hierarchy | Instructions from parent CLAUDE.md are inherited (root applies everywhere) |
Implication: Per-package CLAUDE.md must include everything Claude needs for that package. Don't assume root-level detail will be available — keep root lean, push detail down.
Context Budget
| Component | Max Lines | Rationale |
|---|---|---|
| Root CLAUDE.md | 300 | Always loaded — keep it to project-wide info only |
| Root @import files | 500 each | Lazy-loaded by topic, shared reference material |
| Per-package CLAUDE.md | 50-200 | Lazy-loaded per directory, focused on that package |
| Per-app CLAUDE.md | 80-250 | Lazy-loaded per directory, apps are more complex |
Why these limits:
- Root is ALWAYS in context — every token counts
- Per-package files load only when needed — can be more detailed
- But still concise — tables over prose, no duplication
When to Use
- After monorepo refactor: Single package split into many (e.g.,
packages/core→ 12 packages) - Package added/removed: New package needs CLAUDE.md, removed package's docs are stale
- After running
map-project: Root docs exist but no per-package files - Claude searches blindly: Searching across 15+ packages for something in one package
- Periodic refresh: User asks to update monorepo docs or sync CLAUDE.md files
Symptoms:
- Claude reads 10 files across 5 packages to find one function
- No CLAUDE.md files exist in
packages/*/orapps/*/ - Root CLAUDE.md has 400+ line exports-reference trying to cover all packages
- Claude uses wrong import patterns or outdated types for a specific package
When NOT to Use
- Single-package project: Use
map-projectinstead - Only root needs updating: Use
map-projectinstead - Monorepo with 1-2 packages: Overhead not worth it — root docs suffice
Common Rationalizations (All Wrong)
- "The root CLAUDE.md already covers everything" → Root loads for ALL sessions; per-package loads ONLY when needed. Push detail down.
- "Per-package CLAUDE.md will be redundant" → No — Claude doesn't see root @import files when working in a subdirectory unless they're loaded. Each package must stand alone.
- "I'll just do the important packages" → ALL packages need CLAUDE.md. The one you skip is the one Claude will struggle with.
- "50 lines isn't enough" → Tables compress information 3x. 50 lines of tables = 150 lines of prose.
- "I can copy the same template everywhere" → Each package has different exports, dependencies, and conventions. Customize every file.
- "The root exports-reference.md already has this" → Move it down. Root should have summary counts, not full export tables.
- "Phase 1 takes too long for 15 packages" → Use parallel grep/find commands. Discovery takes 2 minutes. Skipping it costs 20 minutes of rework.
Phase 1: Monorepo Discovery (MANDATORY)
Gate: Complete inventory before writing ANY per-package CLAUDE.md.
Step 1: Find All Packages and Apps
# List all packages
ls -d packages/*/package.json 2>/dev/null | sed 's|/package.json||'
# List all apps
ls -d apps/*/package.json 2>/dev/null | sed 's|/package.json||'
Step 2: Build Package Inventory
For EACH package/app, collect:
# Package name from package.json
cat packages/*/package.json | jq -r '.name'
# Export count from index.ts
for pkg in packages/*/; do
name=$(jq -r '.name' "$pkg/package.json" 2>/dev/null)
count=$(grep -c "^export" "$pkg/src/index.ts" 2>/dev/null || echo 0)
echo "$name: $count exports"
done
Create inventory table:
| Package | Exports | Has Tests | Key Role |
|---|---|---|---|
| @scope/contracts | ? | No | Shared types |
| @scope/config | ? | No | Paths, env |
| ... | ... | ... | ... |
Step 3: Map Inter-Package Dependencies
For each package, find which other monorepo packages it imports:
# For each package, find internal dependencies
for pkg in packages/*/; do
name=$(jq -r '.name' "$pkg/package.json" 2>/dev/null)
deps=$(grep -rh "from \"@" "$pkg/src/" --include="*.ts" 2>/dev/null | \
sed 's/.*from "\(@[^"]*\)".*/\1/' | sort -u | tr '\n' ', ')
echo "$name → $deps"
done
Step 4: Extract Exports Per Package
For each package, list ALL exports:
# Full export listing per package
for pkg in packages/*/; do
name=$(jq -r '.name' "$pkg/package.json" 2>/dev/null)
echo "=== $name ==="
grep "^export" "$pkg/src/index.ts" 2>/dev/null
echo ""
done
Step 5: Identify Key Files Per Package
# List source files per package
for pkg in packages/*/; do
name=$(jq -r '.name' "$pkg/package.json" 2>/dev/null)
echo "=== $name ==="
find "$pkg/src" -name "*.ts" -not -name "*.test.ts" -not -name "*.spec.ts" | sort
echo ""
done
Step 6: Check for Tests
# Find which packages have tests
for pkg in packages/*/; do
name=$(jq -r '.name' "$pkg/package.json" 2>/dev/null)
tests=$(find "$pkg" -name "*.test.ts" -o -name "*.spec.ts" 2>/dev/null | wc -l)
echo "$name: $tests test files"
done
Phase 1 Gate
Before proceeding, you MUST have:
- List of ALL packages with export counts
- List of ALL apps with descriptions
- Inter-package dependency map
- Key files per package
- Test file inventory
- Role description for each package (1-line purpose)
Phase 2: Per-Package CLAUDE.md Generation
Gate: EVERY package in packages/*/ has a CLAUDE.md.
For EACH package, create packages/<name>/CLAUDE.md using the template from references/package-template.md.
Required Sections Per Package
Every per-package CLAUDE.md MUST have:
| Section | Purpose | Format |
|---|---|---|
| Header | Package name + 1-line purpose | # @scope/name + paragraph |
| Exports | ALL exported items | Table: Export, Kind, Purpose |
| Key Files | Source files with descriptions | Table: File, Purpose |
| Dependencies | Which @scope/* packages are imported | Bullet list |
| Import Pattern | How consumers import from this package | Code block |
| Conventions | Package-specific rules | Bullet list or table |
| Testing | How to test (if tests exist) | Command + pattern |
Content Quality Rules
- Export tables must be COMPLETE — every
exportfromindex.tsappears in the table - Key files must list ALL source files — not just "the important ones"
- Dependencies must be accurate — grep the actual imports, don't guess
- Import patterns must show real examples — from actual consumers in the monorepo
- No prose paragraphs — tables and bullet lists only. Save tokens.
Package CLAUDE.md Size Guide
| Package Complexity | Target Lines | Example |
|---|---|---|
| Foundation (types only) | 50-80 | contracts, config |
| Engine (5-15 exports) | 80-120 | session-engine, projects-engine |
| Engine (15-40 exports) | 120-180 | guards-engine, history-engine |
| Engine (40+ exports) | 150-200 | review-engine (with schemas) |
Phase 3: Per-App CLAUDE.md Generation
Gate: EVERY app in apps/*/ has a CLAUDE.md.
Apps are more complex than packages. Use the template from references/app-template.md.
Required Sections Per App
Every per-app CLAUDE.md MUST have:
| Section | Purpose | Format |
|---|---|---|
| Header | App name + purpose | # @scope/name + paragraph |
| File Structure | Directory tree with descriptions | Tree + table |
| Entry Points | Main files and their roles | Table |
| Commands | Build, start, test commands | Table |
| Dependencies | Which @scope/* packages are imported | Table with purpose |
| Key Patterns | Architecture patterns used | Descriptions + examples |
App-Specific Sections
For API/Server apps, also include:
- Route table (ALL routes with methods, handlers)
- Middleware stack
- Request/response patterns with types
For CLI apps, also include:
- Command table (all commands with descriptions)
- Hook handler table (if applicable)
- stdin/stdout patterns
For Web UI apps, also include:
- Pages table (route → component)
- Key components list
- State management patterns
- API integration patterns
App CLAUDE.md Size Guide
| App Type | Target Lines | Example |
|---|---|---|
| API server | 150-250 | api (routes, middleware, patterns) |
| CLI tool | 120-200 | cli (commands, hooks, entry points) |
| Web UI | 120-200 | web-ui (pages, components, patterns) |
Phase 4: Root Simplification
Gate: Root CLAUDE.md + reference files simplified. No per-package detail in root.
After generating per-package CLAUDE.md files, simplify the root:
What STAYS in Root CLAUDE.md
- Project name + 1-line description
- Package map table (name + 1-line purpose — NO export counts)
- Global conventions (ESM, bare imports, build commands)
- Quick reference table (which file to read)
- @imports for shared reference files
- Skills, agents, plugins table
- Hook handlers table (cross-cutting concern)
- Type gotchas (cross-cutting concern)
What MOVES to Per-Package CLAUDE.md
- Per-package export tables → each package's own CLAUDE.md
- Per-package file listings → each package's own CLAUDE.md
- Package-specific conventions → each package's own CLAUDE.md
What STAYS in Root Reference Files
architecture.md:
- High-level dependency graph (package relationships)
- Data flow diagrams (cross-package flows)
- State machines (cross-cutting concepts)
- API routes table (quick lookup — detail in apps/api/CLAUDE.md)
development-guide.md:
- Cross-cutting "how to add a new X" guides
- Response formats (shared across API)
- Error handling patterns (shared)
- Testing patterns (shared)
exports-reference.md:
- Summary table ONLY (package name + export count + 1-line purpose)
- Import patterns section (how to import from each package)
- Remove per-export detail — that now lives in per-package CLAUDE.md
Root Simplification Checklist
- Root CLAUDE.md is ≤ 300 lines
- exports-reference.md has summary table only (≤ 150 lines)
- architecture.md has no per-package file listings (≤ 400 lines)
- development-guide.md has no per-package export tables (≤ 500 lines)
- No per-package detail duplicated between root and subdirectory files
Phase 5: Verification (MANDATORY)
Gate: ALL checks pass before marking complete.
Check 1: Coverage
# Count packages/apps that need CLAUDE.md
TOTAL=$(ls -d packages/*/package.json apps/*/package.json 2>/dev/null | wc -l)
# Count generated CLAUDE.md files
GENERATED=$(ls packages/*/CLAUDE.md apps/*/CLAUDE.md 2>/dev/null | wc -l)
echo "Coverage: $GENERATED / $TOTAL"
FAIL if: Any package/app is missing CLAUDE.md
Check 2: Export Completeness
For each package, verify export coverage:
for pkg in packages/*/; do
actual=$(grep -c "^export" "$pkg/src/index.ts" 2>/dev/null || echo 0)
documented=$(grep -c "^|" "$pkg/CLAUDE.md" 2>/dev/null || echo 0)
name=$(basename "$pkg")
echo "$name: $documented documented / $actual exports"
done
FAIL if: Any package has < 90% export coverage
Check 3: Context Budget
# Root files
wc -l CLAUDE.md
wc -l .claude/claude-md-refs/*.md
# Per-package files
for f in packages/*/CLAUDE.md apps/*/CLAUDE.md; do
wc -l "$f"
done
FAIL if:
- Root CLAUDE.md > 300 lines
- Any per-package CLAUDE.md > 200 lines
- Any per-app CLAUDE.md > 250 lines
Check 4: Self-Containment
For each per-package CLAUDE.md, verify it answers:
- What does this package do? (header)
- What does it export? (exports table)
- What files does it contain? (key files)
- What does it depend on? (dependencies)
- How do I import from it? (import pattern)
Check 5: No Duplication
Verify per-package export details are NOT duplicated in root reference files:
- exports-reference.md has summary table only — no per-export rows
- Root CLAUDE.md has no per-package file listings
- Per-package CLAUDE.md does not repeat root conventions
Check 6: Root Simplified
- Root CLAUDE.md is ≤ 300 lines (down from previous size)
- exports-reference.md is ≤ 150 lines (summary only)
- No per-package detail remains in root files
Quality Checklist (Must Score 10/10)
Coverage (0-2 points)
- 0: Some packages missing CLAUDE.md
- 1: All packages have CLAUDE.md but some incomplete
- 2: Every package and app has complete CLAUDE.md
Export Completeness (0-2 points)
- 0: <50% of exports documented per package
- 1: 50-89% coverage
- 2: >90% coverage in every package
Self-Containment (0-2 points)
- 0: Per-package docs reference root files for basic info
- 1: Mostly self-contained but missing some context
- 2: Each CLAUDE.md is fully self-contained for its package
Root Simplification (0-2 points)
- 0: Root still has per-package detail
- 1: Some detail moved but root still verbose
- 2: Root is lean (≤300 lines), detail pushed to packages
Context Efficiency (0-2 points)
- 0: Over budget or heavy duplication
- 1: Within budget but some duplication
- 2: Within budget, zero duplication, tables over prose
Total: 10/10 required to complete this skill
Failure Modes
Failure Mode 1: Copy-Paste Templates
Symptom: All per-package CLAUDE.md files look identical with placeholder text Fix: Each file must have actual exports, actual file names, actual dependencies from discovery.
Failure Mode 2: Root Not Simplified
Symptom: Per-package files created but root CLAUDE.md still 500+ lines with full export tables Fix: Phase 4 is mandatory. Move detail down, slim root to ≤300 lines.
Failure Mode 3: Missing Packages
Symptom: 10 of 12 packages have CLAUDE.md, 2 skipped Fix: Check 1 catches this. Every package, no exceptions.
Failure Mode 4: Incomplete Exports
Symptom: Package has 43 exports but CLAUDE.md only lists 20 Fix: Use grep to get actual count, verify table row count matches.
Failure Mode 5: Broken Cross-References
Symptom: Per-package CLAUDE.md references root @import that moved Fix: Per-package files should be self-contained. No @imports to root reference files.
Failure Mode 6: Prose Instead of Tables
Symptom: Per-package CLAUDE.md is 300 lines of paragraphs Fix: Tables compress 3x. A 40-export package needs a 40-row table, not 40 paragraphs.
Quick Workflow Summary
PHASE 1: DISCOVERY (Do not skip)
├── Find all packages/ and apps/ directories
├── Count exports per package
├── Map inter-package dependencies
├── List key files per package
├── Check for test files
└── Gate: Complete inventory
PHASE 2: PER-PACKAGE CLAUDE.md (Every package)
├── Header + purpose
├── Exports table (ALL exports)
├── Key files table
├── Dependencies list
├── Import patterns
├── Conventions + testing
└── Gate: Every package has CLAUDE.md
PHASE 3: PER-APP CLAUDE.md (Every app)
├── Header + purpose
├── File structure + entry points
├── Commands table
├── Dependencies with purposes
├── App-specific sections (routes/commands/pages)
└── Gate: Every app has CLAUDE.md
PHASE 4: ROOT SIMPLIFICATION
├── Slim root CLAUDE.md to ≤300 lines
├── Slim exports-reference.md to summary table only
├── Remove per-package detail from root files
├── Keep cross-cutting concerns in root
└── Gate: Root simplified, no duplication
PHASE 5: VERIFICATION (All must pass)
├── Check 1: Every package/app has CLAUDE.md
├── Check 2: >90% export coverage per package
├── Check 3: Context budget met
├── Check 4: Self-containment verified
├── Check 5: No duplication
├── Check 6: Root simplified
└── Gate: All 6 checks pass
COMPLETE: Announce final quality score (must be 10/10)
Completion Announcement
When done, announce:
Monorepo CLAUDE.md generation complete.
**Quality Score: X/10**
- Coverage: X/2 (N packages + M apps documented)
- Export Completeness: X/2 (>90% per package)
- Self-Containment: X/2
- Root Simplification: X/2 (root: N lines, was M lines)
- Context Efficiency: X/2
**Files created:**
- packages/contracts/CLAUDE.md: X lines
- packages/config/CLAUDE.md: X lines
- [... all packages ...]
- apps/api/CLAUDE.md: X lines
- apps/cli/CLAUDE.md: X lines
- apps/web-ui/CLAUDE.md: X lines
**Root simplified:**
- CLAUDE.md: X lines (was Y)
- exports-reference.md: X lines (was Y)
**Verification passed:** All 6 checks complete.
Integration with Other Skills
map-project— Use that for single-package projects or root-only updates. Use THIS skill for monorepo per-package docs.start— Start skill identifies if monorepo docs are neededrebuild— After rebuild, run this if packages were added/removed
Workflow:
New package added
│
▼
rebuild skill (verify build)
│
▼
map-project-monorepo (this skill)
│
▼
commit skill (commit the new CLAUDE.md files)
References
See references/ for templates:
| Reference | Purpose |
|---|---|
package-template.md |
Template for per-package CLAUDE.md with all required sections |
app-template.md |
Template for per-app CLAUDE.md with app-type-specific sections |
This skill ensures every package and app in a monorepo has focused, self-contained documentation that loads exactly when Claude needs it.