The Foundation That Changes Everything
On this page
I started with a single CLAUDE.md that was fourteen lines long. Project name, a couple of commands, a vague note about “use TypeScript.” It worked for about a week. Then I noticed Claude committing .env files, adding inline styles after I’d explicitly switched to Tailwind, and asking me the same questions about deployment targets every other session. Every conversation started cold, and I was spending the first ten minutes re-establishing context I’d already given.
The turning point wasn’t a tool or a plugin — it was realizing that Claude Code reads certain files automatically at the start of every session, and that I was barely using that surface. CLAUDE.md isn’t a README. It’s a briefing document, and the quality of every session is bounded by what’s in it.
Over the past few months, I’ve iterated on a layered system that gives Claude the right context at the right time — root instructions for the stuff that always matters, scoped rules that load conditionally, and a safety hook that catches destructive commands before they land. This post covers that foundation. Everything else in the series builds on top of it.
The Full Stack
This is the first post in a series of six. Each one adds a layer to the workflow, and each layer is independently useful — you don’t need to adopt all six to get value from any one of them. Here’s what the full system looks like:
flowchart TD
subgraph post1["Post 1 — Foundation ★"]
A["Root Instructions\n(CLAUDE.md)"]
B["Domain Rules\n(.claude/rules/)"]
end
subgraph post2["Post 2 — Knowledge Base"]
C["Knowledge Vault\n(sessions, features, lessons)"]
end
subgraph post3["Post 3 — Session Memory"]
D["Session Commands\n(/session-start, /session-end)"]
E["Quality Agents\n(/check, doc-updater)"]
end
subgraph post4["Post 4 — Spec-Driven Dev"]
F["Specifications & Plans\n(/spec-create, writing-plans)"]
end
subgraph post5["Post 5 — Cross-AI Review"]
G["Multi-AI Review\n(/spec-review, /plan-review)"]
end
subgraph post6["Post 6 — Extensions"]
H["Plugins & MCPs\n(Telegram, DevTools, Context7)"]
end
I["Hooks\n(safety, optimization, notification)"]
A --> B
B --> C
C --> D
D --> E
E --> F
F --> G
G --> H
A --> I
I -.->|"Post 1"| B
I -.->|"Post 3"| E
I -.->|"Post 6"| H
style post1 fill:#7c3aed22,stroke:#7c3aed,stroke-width:2px
style post2 fill:#1e293b,stroke:#334155
style post3 fill:#1e293b,stroke:#334155
style post4 fill:#1e293b,stroke:#334155
style post5 fill:#1e293b,stroke:#334155
style post6 fill:#1e293b,stroke:#334155
Root Instructions: CLAUDE.md
CLAUDE.md is a markdown file at the root of your project that Claude Code reads automatically at the start of every session. No import, no flag, no configuration — if the file exists, Claude reads it. This is both the simplest and the most impactful thing you can set up.
Without it, Claude has no idea what your project is. It doesn’t know your tech stack, your deployment target, your naming conventions, or that you switched from Jest to Vitest three months ago. So it guesses. And its guesses are reasonable but wrong — it’ll suggest npm test when your script is npm run test:unit, or reach for CSS modules when you’re using Tailwind. You end up correcting the same things every session.
A good CLAUDE.md eliminates an entire class of friction. It’s the difference between onboarding a contractor with a project brief versus pointing them at the repo and saying “figure it out.”
Anatomy of a Root CLAUDE.md
I’ve settled on a structure with six sections. Not every project needs all of them, but this covers most of what Claude needs to know session after session:
# my-project
Web application — Express API + React frontend.
## CRITICAL Constraints
- **NEVER** commit secrets — use environment variables
- **ALWAYS** run tests before committing
## Commands
| Command | Purpose |
|---------|---------|
| `npm run dev` | Development server |
| `npm run test` | Test suite |
## Tooling
### CLI Tools
| Tool | When to Use |
|------|-------------|
| `gh` | PRs, issues, CI checks, releases |
| `npm` | Package management, running scripts |
## Repository Structure
src/
├── api/ # Express routes and middleware
├── components/ # React components
├── lib/ # Shared utilities
└── styles/ # Tailwind config and globals
## Documentation
| Document | Location |
|----------|----------|
| API reference | `docs/api.md` |
| Architecture | `docs/architecture.md` |
The project description up top gives Claude immediate context about what kind of codebase it’s in. The constraints section establishes hard rules. The commands table means Claude never guesses at script names. The Tooling section surfaces CLI tools like gh so Claude knows to use them for PRs, commits, and issue management — we’ll cover this more in the GitHub CLI section below. The repository structure gives it a mental map so it doesn’t need to explore the file tree every time. And the documentation table points Claude to reference material before it starts guessing.
The companion repo has a complete example you can start from.
Critical Constraints
The constraints section deserves special attention. These are the rules where violations have real consequences — the things you’d flag in a code review immediately. I format them with **NEVER** and **ALWAYS** in bold because Claude Code treats these markers as high-priority directives.
Think of them as the guardrails that prevent the worst outcomes: committing secrets, deploying without tests, adding dependencies you’ve explicitly decided against. Keep the list short. If you have more than five or six constraints, some of them probably belong in domain-specific rule files instead.
Scaling Up: Sub-Project Instructions
For single-stack projects, one CLAUDE.md is plenty. But if your repo has distinct sub-projects — a React frontend and an Express API, say — a single file starts to strain. Frontend conventions bleed into backend sessions, API patterns show up when Claude is editing components, and the file grows past the point where every instruction is relevant to every task.
The fix is sub-project CLAUDE.md files. The root CLAUDE.md keeps the universal stuff — critical constraints, project overview, the commands that apply everywhere. Each sub-directory gets its own CLAUDE.md with stack-specific conventions:
my-project/
├── CLAUDE.md # Universal constraints, project overview
├── frontend/
│ └── CLAUDE.md # React conventions, component patterns
├── backend/
│ └── CLAUDE.md # Express API patterns, DB conventions
└── .claude/
└── rules/ # Glob-scoped rules (loaded conditionally)
Claude Code loads the root CLAUDE.md first, then loads any CLAUDE.md in the directory it’s working in. Sub-project files inherit the root constraints — they add specifics, they don’t repeat universals. The frontend CLAUDE.md might cover component naming patterns, state management conventions, and testing patterns for React. The backend one covers route structure, middleware patterns, and database access conventions.
I’ve found this pattern also scales forward well. If the project eventually needs a mobile app (React Native, Flutter) or a desktop client, each gets its own CLAUDE.md from day one without touching the root or existing sub-projects. The structure anticipates growth without over-engineering it today.
Domain Rules
CLAUDE.md handles the universal stuff — project identity, critical constraints, key commands. But what about conventions that only matter in specific contexts? TypeScript strictness rules aren’t relevant when Claude is editing a CSS file. Deployment procedures don’t matter when it’s refactoring a utility function.
That’s where .claude/rules/ comes in. Each file in this directory has a frontmatter block with glob patterns that tell Claude Code when to load it. The rules only appear in Claude’s context when it’s editing files that match the pattern.
---
globs: "**/*.ts,**/*.tsx"
---
- TypeScript strict mode — no `any` (use `unknown` + narrowing)
- Type-only imports: `import type { Foo }`
- Named exports only (no default exports except page routes)
This one loads when Claude touches any TypeScript file. When it’s editing a CSS file or a config JSON, these rules stay out of the context window entirely. That’s not just tidier — it means Claude isn’t wading through irrelevant instructions, and you’re not paying for tokens that don’t apply.
I typically end up with four or five rule files per project: one for TypeScript conventions, one for styling, one for deployment, one for content/data schemas, and sometimes one for API patterns. Common domains I’ve found useful:
- TypeScript: strict mode, import conventions, exhaustive switches
- Styling: Tailwind-only, no inline styles, design token usage
- Deployment: staging-first flow, build verification, platform constraints
- Content: schema validation, frontmatter requirements, file naming
The companion repo has full examples for each of these.
Project Settings
Claude Code stores shared project configuration in .claude/settings.json. This file gets committed to the repo, so everyone on the team (and every Claude session) starts with the same baseline.
{
"permissions": {
"allow": ["Read", "Edit", "Write", "Glob", "Grep", "Bash"],
"deny": []
},
"additionalDirectories": []
}
The permissions block controls which tools Claude can use without asking. The defaults here are fairly permissive — Claude can read, edit, and run shell commands. You can tighten this if your project has sensitive areas, or loosen it further if you want fewer permission prompts.
There’s also a global ~/.claude/ directory — your user-level config. It holds a global settings.json, per-project memory that persists across conversations, and other state that Claude Code manages automatically. The project’s .claude/settings.json merges with the global one, and project settings take precedence where they overlap. You don’t need to set up the global directory manually — Claude Code creates it — but knowing it exists helps when you’re wondering where persistent memory or global preferences live.
For machine-specific configuration that shouldn’t be committed — local paths, personal hooks, MCP server registrations — there’s settings.local.json in the project’s .claude/ directory. We’ll use it in Post 3 for hook registration, and in Post 6 for MCP server configuration.
Git Setup
Claude’s configuration files are a mix of things that should be shared (so every session and every team member gets the same behavior) and things that should stay local. Getting this split right early saves headaches later.
What to Commit
These are the files that define your project’s Claude behavior — they belong in version control:
CLAUDE.md— root instructions.claude/rules/— domain-specific conventions.claude/settings.json— shared project settings.claude/agents/— custom agent definitions (Post 3).claude/commands/— slash command definitions (Post 3).claude/hooks/— hook scripts (safety guards, quality gates).claude/templates/— templates for specs, plans, notes (Post 4)
What to Ignore
Ephemeral output and machine-specific config should stay out of the repo:
# Claude Code ephemeral output
.claude/review-output/
.claude/settings.local.json
Add these patterns to your .gitignore. The review output directory accumulates temporary files from cross-AI reviews (Post 5), and settings.local.json contains machine-specific paths and hook registrations that would break on another developer’s machine.
For commit messages, I follow a type: description convention — feat:, fix:, docs:, chore:. Claude picks this up quickly if you mention it in CLAUDE.md, and it keeps the git log scannable. Not critical, but a nice quality-of-life detail.
Essential Tooling: The GitHub CLI
Claude Code can run any CLI tool you have installed, but it doesn’t automatically know which ones are available or when to reach for them. That’s where the Tooling section of CLAUDE.md comes in — it’s how you make Claude aware of the CLI tools it should be using for specific operations.
The most important one to surface is gh, GitHub’s official CLI. Claude Code uses it for creating pull requests, managing issues, checking CI status, and code search. Without it listed in CLAUDE.md, Claude might try to push and create PRs through raw git commands, or worse, skip the PR workflow entirely.
I add a CLI Tools table to my CLAUDE.md alongside the project commands:
### CLI Tools
| Tool | When to Use |
|------|-------------|
| `gh` | PRs, issues, CI checks, releases |
| `npm` | Package management, running scripts |
This is enough for Claude to know that gh pr create is available, that it should use gh run list to check CI before merging, and that gh issue list can surface context about open work. The Tooling section isn’t just documentation — it’s how you shape Claude’s behavior around operations like committing and creating PRs.
If you don’t have gh installed, it’s a quick setup:
# Ubuntu/Debian
sudo apt install gh
# macOS
brew install gh
# Then authenticate
gh auth login
One thing I’ve found valuable about gh over MCP-based GitHub integrations: it keeps everything in the terminal. No extra context window overhead from MCP tool call metadata, no additional server processes. It’s just a CLI tool that Claude runs like any other shell command.
Safety First: The Pre-Tool Guard
Hooks are scripts that Claude Code runs automatically before or after specific tool invocations. They intercept commands, inspect them, and can block execution before anything happens. Think of them as git hooks, but for your AI coding assistant.
Without any guardrails, Claude can run destructive commands. It usually doesn’t — it’s generally cautious — but “usually” isn’t good enough when the downside is git reset --hard wiping uncommitted work. I learned this the hard way after asking Claude to “clean up the branch” and watching it nuke a day’s worth of changes that hadn’t been committed yet. That was the last time I ran Claude without a pre-tool guard.
What It Blocks
The guard intercepts five patterns that have no business running in an AI-assisted session:
git clean -f— deletes untracked files permanently. There’s no undo.git checkout -- .— discards all unstaged changes in the working directory. Again, no undo.git reset --hard— resets the branch and working tree, destroying uncommitted work.force push to main— rewriting shared history on the main branch is almost never what you want.git add .env— committing secrets to the repo. Even if you remove them later, they’re in the git history.
Each of these is a command that a human might run deliberately after careful thought, but that an AI should never run automatically. The guard doesn’t prevent Claude from doing its work — it prevents the specific operations where the blast radius is unrecoverable.
How It Works
The hook is a bash script registered in your settings file. It runs before every Bash tool invocation, inspects the command, and either allows it (exit 0 with an “approve” decision) or blocks it (exit 0 with a “block” decision and an explanation).
Here’s the hook registration that goes in your settings:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [{
"type": "command",
"command": "bash .claude/hooks/pre-tool-guard.sh"
}]
}
]
}
}
This goes in settings.local.json, which we’ll set up fully in Post 3 when we wire up session commands and quality agents alongside the safety guard. The full script is in the companion repo.
What’s Next
With CLAUDE.md, domain rules, project settings, and a safety hook, every Claude Code session starts with the right context and the right guardrails. That alone is a significant improvement over the default experience — but it’s still stateless. Every session starts fresh, with no memory of what happened yesterday.
In the next post, we’ll build the knowledge base layer: a structured vault where session notes, feature documentation, architectural decisions, and debugging insights accumulate over time. It’s what gives your project institutional memory — so Claude doesn’t just know your conventions, it knows your history.
All files from this post are available in the companion repo.
Stay in the loop
Get notified when I publish new posts. No spam, unsubscribe anytime.
Your email is stored by ConvertKit. Privacy policy