Claude Code Project Structure: Best Practices for Real Repos

What goes in CLAUDE.md, what goes in the .claude/ folder, which MCP servers belong in the repository and which belong to you, what stays out of git, and why your list of work should not be a file in the repo.

6 min read

A well-structured repository for Claude Code has a short CLAUDE.md at the root, a .claude/ folder holding the team’s settings, rules, skills and subagents (opens in a new tab), and a .mcp.json only for tool servers the whole team uses. Personal files — CLAUDE.local.md and .claude/settings.local.json — stay out of git, and so do secrets. The list of work stays out of the repository altogether. Here is the layout, then what earns a place in each part.

One repository, laid out for Claude Code
my-app/
  CLAUDE.md                  # committed: what Claude needs every session
  CLAUDE.local.md            # yours only: gitignored
  .mcp.json                  # committed: team MCP servers, no secrets
  .claude/
    settings.json            # committed: permissions, hooks, env
    settings.local.json      # yours only: kept out of git
    rules/                   # committed: topic or path-scoped instructions
    skills/<name>/SKILL.md   # committed: procedures, loaded on demand
    agents/                  # committed: subagent definitions

CLAUDE.md: only what earns a line

Claude Code loads CLAUDE.md into context at the start of every session, so every line costs something in every session. The documentation’s target (opens in a new tab) is under 200 lines, and its test for adding a line is practical: Claude made the same mistake twice, a review caught something Claude should have known, or you typed the same correction you typed last time.

What belongs there:

  • Commands Claude cannot guess: how to build, test and lint, and from which directory.
  • Conventions that differ from the defaults: “pnpm, never npm”, “dates are stored in UTC”.
  • Hard lines: “never edit files under generated/; run pnpm codegen”.
  • The one or two traps that cost you an afternoon last month.

What does not:

  • Anything Claude can read from the code. The /doctor checkup proposes trimming directory layouts, dependency lists and architecture overviews for exactly that reason.
  • Multi-step procedures, such as how to cut a release. Those are skills.
  • Rules for one corner of the codebase. Those are path-scoped rules, or a CLAUDE.md in that directory.
  • Anything that must happen every time. CLAUDE.md is context Claude tries to follow, not configuration it must obey; for a guarantee, use a hook or a permission rule.

Write instructions that can be checked: “run pnpm test before committing” rather than “test your changes”. /init drafts a starting file from the codebase, and block-level HTML comments are stripped before the file reaches Claude, so you can leave notes for human maintainers at no cost. If the repository already has an AGENTS.md for other assistants, keep that as the single source and put @AGENTS.md at the top of CLAUDE.md.

.claude/settings.json: what Claude may do

Settings are where rules become enforced. The project file (opens in a new tab) holds permissions, hooks and environment variables, and is committed so everyone who clones the repository gets the same ones. Each person can override it in .claude/settings.local.json; when Claude Code creates that file itself it also adds it to your global git excludes, and if you create it by hand you add it to .gitignore yourself.

.claude/settings.json
{
  "permissions": {
    "deny": ["Read(./.env)", "Read(./**/dist/**/*)"]
  },
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [{ "type": "command", "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write" }]
      }
    ]
  }
}

The deny rules keep Claude’s file tools away from secrets and build output; the hook formats every file (opens in a new tab) Claude edits, whatever Claude decides. Hooks live under the hooks key in a settings file, not in a folder of their own. When the same key is set in several places, the order from highest is managed settings, command-line flags, local project, shared project, then your user settings, and permission lists merge across them rather than replacing each other.

rules/, skills/ and agents/

Three folders move instructions out of CLAUDE.md so they load only when relevant:

  • .claude/rules/ — one Markdown file per topic. A rule without frontmatter loads every session, like CLAUDE.md. A rule with a paths: glob, such as src/api/**/*.ts, loads only when Claude works with a matching file.
  • .claude/skills/<name>/SKILL.md — a procedure with a name and a description. Claude loads it when it judges it relevant, or you run it as /name. Custom commands have been merged into skills (opens in a new tab): an old .claude/commands/deploy.md still works, but a skill folder can also carry supporting files, so prefer it for anything new.
  • .claude/agents/ — subagent definitions, one Markdown file each, with their own prompt, tools and model. When to use them is covered in Claude Code Task tool vs subagents.

The same folders exist under ~/.claude/ for things that are yours in every project. The split is the same as for settings: if a teammate would need it to work in this repository, commit it here; if it is your habit, keep it in your home directory.

.mcp.json or user scope

Claude Code stores MCP servers at three scopes (opens in a new tab). Local (the default) and user scope live in ~/.claude.json, outside the repository: local for one project, user for all of them. Project scope lives in .mcp.json at the repository root and is meant to be committed. Claude Code asks for approval before using a server from .mcp.json in an interactive session, and the documentation says plainly not to commit secrets to it.

A working rule for which is which:

  • .mcp.json — servers everyone on the repository needs to do the work: a documentation server, a local database tool. Where one needs a key, reference an environment variable with ${API_KEY} so the value never enters git.
  • User scope — servers that act as you, with your own sign-in: your task board, your mail, your calendar. Nothing about them belongs in the repository, and one entry serves every project on your machine.
Terminal — a personal server at user scope
claude mcp add --scope user --transport http fenbs https://fenbs.ai/api/mcp

For a server that signs in through the browser after /mcp, Claude Code looks after the token itself — the MCP documentation says it is stored securely and refreshed automatically — so there is nothing to put in the repository. If the same name is defined at more than one scope, local wins over project, and project over user.

What stays out of git

  • CLAUDE.local.md — your sandbox URLs and test data. Add it to .gitignore.
  • .claude/settings.local.json — your personal allow rules and overrides.
  • Tokens and keys, in any file. Use environment variables or browser sign-in.
  • Claude Code’s own state — ~/.claude.json, auto memory in ~/.claude/projects/, session transcripts — already lives in your home directory, not the repository. Keep it there.

Where tasks live: not in a TODO file

The last thing people add to a repository for Claude is a TODO.md or PLAN.md for it to work through. It seems tidy, and it goes wrong in predictable ways. It changes on every branch, so two branches disagree about what is done. Every update is a commit, so the history of the work is mixed into the history of the code. Nobody outside the repository can see it. And it records no author: a line ticked by Claude and a line ticked by you look the same.

Claude’s own task list is not the answer either; it is a checklist for the steps of one job, kept on your machine, as Claude Code tasks vs to-dos explains. Work people need to see belongs on a board connected over MCP. With fenbs, the repository’s only trace is a few lines in CLAUDE.md naming the board’s project and telling Claude when to read and update it — the lines in a task-tracking workflow for Claude Code. Each task carries its kind, its lane and a comment saying what changed, and the history records whether you or the assistant made each change.

Related

Working across several repositories or a monorepo: managing multiple projects in Claude Code with CLAUDE.md. Deciding which MCP servers to trust: MCP security best practices. Connecting the board: Claude Code integration.

Questions people ask.

Should .claude/ be committed to git?

Mostly, yes. Commit settings.json, rules, skills and agents so the team shares them. Keep settings.local.json out of git; Claude Code adds it to your global git excludes when it creates the file, and you add it to .gitignore yourself if you made it by hand.

Where does .mcp.json go?

At the repository root, not inside .claude/. It holds project-scoped MCP servers and is meant to be committed, without secrets. Servers added at local or user scope are stored in ~/.claude.json instead.

How long should CLAUDE.md be?

The Claude Code documentation targets under 200 lines. Move procedures into skills and area-specific rules into .claude/rules with a paths glob, so they load only when relevant.

Are .claude/commands still supported?

Yes. Custom commands have been merged into skills, and existing files in .claude/commands keep working. For new work a skill folder is preferred, because it can also hold supporting files.

Start with one thing.

There is nothing to set up first. Write one line and you’ve started.