Issue 07 | Writing CLAUDE.md: A Persistence Layer Project Brief for AI
🎯 Learning Objectives
By the end of this issue, you will master:
- The working principles and file loading mechanism of
CLAUDE.md - How to write effective project instructions
- Configuration and management of the Auto Memory feature
- Organizing rules in directories with
.claude/rules/
📖 Core Concepts Explained
7.1 Why is CLAUDE.md needed?
Every time you start a new Claude Code session, it knows nothing about your project. It will explore via Glob and Read, but this is time-consuming and imprecise.
CLAUDE.md is your project brief for Claude—it's automatically loaded into the context at the start of each session, allowing Claude to know from the first second:
- What this project is
- What tech stack is used
- What are the strict rules
- What are the common commands
7.2 CLAUDE.md Placement and Priority
File Location Scope Load Time
────────────────────────────── ────────── ──────────
/Library/Application Support/ System-level Always loaded (admin configured)
ClaudeCode/CLAUDE.md
~/.claude/CLAUDE.md User-level Always loaded (personal preference)
./CLAUDE.md Project-level Loaded when entering project directory
./.claude/CLAUDE.md Project-level (alternative) Same as above
./src/billing/CLAUDE.md Subdirectory-level Loaded when Claude accesses this directory
📚 Official Documentation Reference: How Claude remembers your project
7.3 Referencing Other Files (@import)
CLAUDE.md supports using @ syntax to include other files as context:
# Project Brief
See @README.md for project overview
See @package.json for available npm commands
## Git Workflow
- Follow @docs/git-instructions.md
## Personal Preferences
- @~/.claude/my-personal-style.md
7.4 Auto Memory
In addition to manually writing CLAUDE.md, Claude Code also has an Auto Memory feature—it automatically saves your corrected preferences during a session:
Scenario Example:
You: Don't use semicolons
Claude: Okay, I won't add semicolons in the future
→ Automatically saved to ~/.claude/memory.json:
"Do not use JavaScript semicolons in this project"
💻 Simulating Claude TUI Interaction
Scenario 1: Initializing CLAUDE.md
$ cd ~/my-project
$ claude
> /init
╭─ Claude ─────────────────────────────────────────────╮
│ │
│ 🔍 Tool: Read (package.json) │
│ 🔍 Tool: Glob (**/*.{ts,tsx,js,jsx}) │
│ 🔍 Tool: Read (tsconfig.json) │
│ 🔍 Tool: Read (.eslintrc.json) │
│ 🔍 Tool: Glob (**/Dockerfile) │
│ 🔍 Tool: Read (.github/workflows/*.yml) │
│ │
│ I have analyzed your project structure and generated the following CLAUDE.md: │
│ │
│ ⚙️ Tool: Write → CLAUDE.md │
│ ╭─ NEW FILE ──────────────────────────────────╮ │
│ │ # Project: my-project │ │
│ │ │ │
│ │ ## Overview │ │
│ │ E-commerce platform built with Next.js 14 │ │
│ │ and Express API, deployed on Vercel + │ │
│ │ Railway. │ │
│ │ │ │
│ │ ## Tech Stack │ │
│ │ - Frontend: Next.js 14 (App Router) + React │ │
│ │ - Backend: Express + Prisma + PostgreSQL │ │
│ │ - Styling: Tailwind CSS │ │
│ │ - Testing: Vitest + Playwright │ │
│ │ │ │
│ │ ## Commands │ │
│ │ - `pnpm dev` — start dev servers │ │
│ │ - `pnpm test` — run unit tests │ │
│ │ - `pnpm build` — production build │ │
│ │ - `pnpm db:push` — push schema changes │ │
│ │ │ │
│ │ ## Rules │ │
│ │ - Use TypeScript strict mode │ │
│ │ - 2-space indentation │ │
│ │ - No default exports (use named exports) │ │
│ │ - Commit messages follow Conventional │ │
│ │ Commits format │ │
│ ╰──────────────────────────────────────────────╯ │
│ │
│ ✅ CLAUDE.md created! │
│ You can edit and supplement it as needed. │
│ │
╰──────────────────────────────────────────────────────╯
Scenario 2: CLAUDE.md in Effect
> Create a new API route to handle user profile updates
╭─ Claude ─────────────────────────────────────────────╮
│ │
│ 📝 Loaded context from CLAUDE.md: │
│ • TypeScript strict mode ✓ │
│ • Named exports ✓ │
│ • Conventional Commits ✓ │
│ │
│ ⚙️ Tool: Write → src/routes/profile.ts │
│ ╭─ NEW FILE ──────────────────────────────────╮ │
│ │ import { Router, Request, Response } │ │
│ │ from 'express'; │ │
│ │ import { z } from 'zod'; │ │
│ │ import { prisma } │ │
│ │ from '@my-project/db'; │ │
│ │ import { authMiddleware } │ │
│ │ from '../middleware/auth'; │ │
│ │ │ │
│ │ // ✅ Named export (no default) │ │
│ │ export const profileRouter = Router(); │ │
│ │ │ │
│ │ const updateSchema = z.object({ │ │
│ │ name: z.string().min(1).max(100), │ │
│ │ bio: z.string().max(500).optional(), │ │
│ │ }); │ │
│ │ │ │
│ │ // ✅ 2-space indentation │ │
│ │ profileRouter.patch( │ │
│ │ '/profile', │ │
│ │ authMiddleware, │ │
│ │ async (req: Request, res: Response) => { │ │
│ │ const data = updateSchema.parse( │ │
│ │ req.body │ │
│ │ ); │ │
│ │ const user = await prisma.user.update({ │ │
│ │ where: { id: req.userId }, │ │
│ │ data, │ │
│ │ }); │ │
│ │ res.json({ ok: true, user }); │ │
│ │ } │ │
│ │ ); │ │
│ ╰──────────────────────────────────────────────╯ │
│ │
│ Note: I followed the rules in CLAUDE.md: │
│ ✅ TypeScript strict mode │
│ ✅ Named export (no default export) │
│ ✅ 2-space indentation │
│ ✅ Used existing prisma + zod dependencies in the project │
│ │
╰──────────────────────────────────────────────────────╯
Scenario 3: Managing Auto Memory
> /memory
╭─ Auto Memory ────────────────────────────────────────╮
│ │
│ 📁 Storage: ~/.claude/memory.json │
│ │
│ Saved memories for this project: │
│ 1. "Do not use semicolons (no semicolons)" │
│ 2. "API return format is uniformly { ok: boolean, data: T }" │
│ 3. "Error handling uses custom AppError class" │
│ 4. "Database queries are placed in the services/ directory" │
│ │
│ [e] Edit [d] Delete [q] Quit │
│ │
╰──────────────────────────────────────────────────────╯
💻 Code Demo: Advanced CLAUDE.md Template
# My SaaS Project
## Overview
B2B SaaS platform for HR management.
50K MAU, multi-tenant architecture.
## Tech Stack
- Frontend: Next.js 14 (App Router) + React 19
- Backend: tRPC + Prisma + PostgreSQL
- Auth: Clerk
- Payments: Stripe
- Deployment: Vercel + Supabase
## Commands
- `pnpm dev` — start all services
- `pnpm test` — run Vitest
- `pnpm test:e2e` — run Playwright
- `pnpm db:push` — push schema changes
- `pnpm db:seed` — seed test data
## Architecture Rules
- Use Server Components by default, 'use client' only when needed
- All API routes go through tRPC procedures
- Database queries MUST go through service layer (src/services/)
- Never import from `@prisma/client` directly, use `@/lib/db`
## Code Style
- 2-space indentation
- No semicolons
- Named exports only (no default exports)
- Use `type` imports: `import type { User } from '@/types'`
- Error messages in English, UI text supports i18n
## Git Workflow
- Branch naming: `feat/`, `fix/`, `chore/`
- Commit messages: Conventional Commits
- Always create PR, never push to main directly
## Testing
- Unit tests: colocate with source (*.test.ts)
- E2E tests: tests/e2e/
- Minimum 80% coverage for new code
## Important Context
- @README.md for project overview
- @docs/api-design.md for API conventions
- @.env.example for required environment variables
🔧 Tools / Commands Involved
| Tool/Command | Function |
|---|---|
/init |
Automatically generates CLAUDE.md |
/memory |
View/Edit Auto Memory |
@path/to/file |
Includes other files in CLAUDE.md |
.claude/rules/*.md |
Directory-based rules (split by function) |
~/.claude/CLAUDE.md |
Global personal preferences |
CLAUDE.md |
Project-level instructions |
📝 Key Takeaways from This Issue
CLAUDE.mdis the project's persistent context, automatically loaded in each session- The
/initcommand can one-click generate an initial version - Using
@filepathcan include additional files as context - Auto Memory automatically saves your corrected preferences
- A good CLAUDE.md should be specific and clear: use "2-space indentation" instead of "format properly"