| name | setting-up-claude-ready-repos |
|---|---|
| description | Audit or set up a code repository so Claude Code works efficiently in it — CLAUDE.md, path-scoped .claude/rules, subtree memory, verify scripts, pre-commit hooks, shared settings.json, agents and skills. Use this whenever the user wants to "set up a repo for Claude", "make Claude work better in this codebase", write or trim a CLAUDE.md, add .claude/rules, onboard a new or existing repo to Claude Code, review their Claude setup, or asks why Claude keeps making the same mistakes in a project — even if they don't say "audit" or "scaffold". |
A repo works well with Claude when three things hold:
- Claude loads only what the task needs. Small, scoped instructions beat one big file. Every always-loaded line costs context in every session.
- Every requirement has one home. Two copies drift, and Claude can't tell which is right.
- Code enforces what matters. A failing check beats a sentence Claude might skim past.
Everything below serves those three.
- Inventory the repo (commands below). Record every trap with evidence.
- Score it against the checklist. Report the level, gaps, and a plan.
- Stop and show the plan. You're about to add files that shape every future session. If the user said "just do it", keep the plan short and go on unless they object.
- Scaffold , smallest layer first, from the templates.
- Verify with the checks at the end. Fix what they flag.
- Report : what you created, what you left alone, what the user must decide, and each trap with its evidence.
Everything you write must trace back to something you saw in the repo. An invented command is worse than a missing one, because Claude will trust it.
Run from the repo root. Read-only.
ls package.json pnpm-workspace.yaml pyproject.toml go.mod Cargo.toml pom.xml \
build.gradle* Makefile .nvmrc .tool-versions .python-version .java-version 2>/dev/null
node -e 'const p=require("./package.json");console.log(p.scripts,p.packageManager)' 2>/dev/null
grep -nE '"[~^][0-9]' package.json # unpinned versions
git ls-files | grep -E '(^|/)(tsconfig[^/]*\.json|jest\.config\.|vitest\.config\.|pytest\.ini|package\.json|pyproject\.toml)'
git grep -lIiE 'auto-?generated|do not edit|generated by' | head -20
git grep -nIiE 'must (stay|not|run)|do not (remove|change)|load-bearing|keep this' \
-- '*.json' '*.js' '*.mjs' '*.sh' '*.yml' '*.yaml' '*.toml' Makefile | head -20
ls CLAUDE.md AGENTS.md .claude/settings.json .claude/settings.local.json 2>/dev/null
git ls-files | grep -E '(^|/)CLAUDE\.md$|^\.claude/'
ls .husky .pre-commit-config.yaml lefthook.yml .eslintrc* eslint.config.* 2>/dev/null
Then read what it points at. Look for what a model would guess wrong:
- Commands with non-obvious flags, ordering, or look-alike names.
- Settings whose removal breaks something far away.
- Generated files, and the command that regenerates them.
- Folders with their own tsconfig, test runner, or language.
- Import boundaries between layers or packages.
- Scripts that are broken today. Note them as known failures. Don't fix them unasked.
Mark each item present, partial, or missing, and cite the file. The level is the highest one whose items are all present. Most value comes from reaching level 2, so lead with that.
Level 1: Claude can run the project
- Root
CLAUDE.mdwritten by a person, not raw/initoutput. - Every command in
CLAUDE.mdexists in the package scripts or Makefile. - Named scripts for build, test, lint, type-check, plus one "check everything" and one "fix everything".
- Toolchain pinned (
.nvmrc,packageManager,.python-version, ...). - Generated paths named, with the regenerate command.
- Load-bearing settings named, with what breaks without them.
Level 2: context is scoped
- Root
CLAUDE.mdunder ~150 lines, no pasted docs. .claude/rules/*.mdwith apaths:glob on every rule that isn't truly repo-wide. Every glob matches at least one file.- Each rule has Requirements and Where to Look, and no large code blocks.
.claude/rules/README.mdindexes every rule with its glob.- Subtree
CLAUDE.mdwherever the toolchain changes, and nowhere else. - No requirement stated in two places.
- A doc map reaches every doc.
Level 3: key rules are enforced
- Layer boundaries enforced by lint import restrictions.
- Generated output checked in the build.
- Pre-commit hook: auto-fix staged files, then run the full check.
- Each custom check opens with a comment naming what it prevents.
- Dependency versions pinned exactly, or a stated reason why not.
Level 4: the loop is fast and shared
- Incremental type-check. Slow generators skip when inputs are unchanged.
- The full check skips what the diff can't affect, or runs in under a minute.
.claude/settings.jsoncommitted: read-only allowlist, generated paths denied forEdit/Write, deploy and publish set toask.- Agents for deep domain knowledge. Skills for repeated multi-step chores.
Skills that install or change the environment set
disable-model-invocation: true.
Level 5: writer and reviewer agree
- A PR review bot reads the same rules (thin wrappers, same globs).
- PR title, commit, and evidence rules exist, with the reason for each.
- Known pre-existing failures are listed, so Claude doesn't chase them.
Always flag, at any level
- Secrets, webhook URLs, signed links, or tokens in committed config.
- Counts or file names in
CLAUDE.mdthat no longer match the tree. - Rules that contradict the code. The code wins, so fix the rule.
- Commands in docs that fail when run.
Each step is useful alone. Stop where the repo's size says to stop.
- Named scripts. Add "check everything" and "fix everything" if missing. Point out unpinned versions. Don't change a version without asking.
- Root
CLAUDE.md. Traps and pointers, not a tour. If one exists, it's the user's work: restructure it, keep every fact that's still true, move requirements into rules, and show what moved where. - Path-scoped rules plus the rules README.
- Subtree
CLAUDE.md, only at toolchain boundaries. - Enforcement. Turn checkable rules into checks. Add the pre-commit hook.
- Shared
.claude/settings.json. Personal entries stay insettings.local.json, which must be git-ignored. - Optional: agents, skills, review-bot wrappers.
Small repos: a single package may need only a root CLAUDE.md, two or
three rules, a hook, and settings.json. Don't build ten layers for 500 lines
of code.
Unknown intent: when you can't tell why a setting exists, list it as a question for the user. Don't mark it load-bearing and don't delete it.
Replace every <...>. Delete sections with nothing true to say.
<One or two sentences: what it is, and the one structural fact that surprises
newcomers (monorepo? two stacks? generated code?).>
## Essential commands
``` bash
<pm> run build # <what it produces; any required step before it>
<pm> run test # <which runners, over which folders>
<pm> run check-all # read-only: format, lint, types, verify:*
<pm> run check-all:fix
Use the named script. Don't split check-all into hand-run tools.
Traps #
<flag>is load-bearing. <What breaks without it.> Do not clean it up.<cmd A>is not<cmd B>. <What each does.> Use <A> for <case>.- <Error> looks like <cause X>. The real cause is <Y>. Fix: <Z>.
Always-true patterns #
- Never hand-edit
<generated path>. Change<source>, run<pm> run <gen>. - <Banned construct, and what to use instead.>
Known failures #
<Pre-existing breaks, so Claude doesn't chase them.>
Where things are #
Rules: .claude/rules/README.md (they load by the path you edit).
Docs: <docs/README.md>. Templates: <docs/templates/>. #
paths:
- "<subtree>/**/*.<ext>"
Requirements #
- MUST <requirement>. <Reason, if not obvious.>
- MUST NOT <anti-pattern>. Enforced by
<lint rule or verify script>. - SHOULD <default>. <When deviating is fine.>
Where to Look #
<templates/x-template.ext>- canonical structure<src/real/example.ext>- a real, current example
Rules hold requirements and pointers. Examples live in docs and real code, so
rules stay short and the examples stay correct. Scope every glob to one
subtree, so a UI rule never loads for server work. Only rules that apply to
everything (PR format, repo hygiene) go without `paths:`.
Rules load by their paths: glob when Claude edits a matching file. Rules
without paths: load every session, so keep those few.
| Rule | Loads for |
|---|---|
<name>.md |
<glob> |
<pull-requests>.md |
always |
A glob can't say "this folder uses a different test runner." A subtree file can. It carries orientation only. It never restates a requirement.
| Type-check | <pm> run type-check:<name> (<tsconfig>) |
| Test | <pm> run test:<name> (<runner>) |
| One test | <pm> exec <runner> run <path/one.spec.ts> |
Boundary: <the only public entry point; what outside code must not import; the lint rule that enforces it>.
Rules that fire here: <rule files whose globs match this tree>. { "permissions": { "allow": ["Bash(git status)", "Bash(git diff:)", "Bash(git log:)", "Bash(<pm> run test:)", "Bash(<pm> run check-all)"], "ask": ["Bash(<pm> run deploy:)", "Bash(<pm> publish:)", "Bash(git push:)"], "deny": ["Edit(<generated path>/)", "Write(<generated path>/)"] } }
`deny` on generated paths stops edits at the harness, not by convention.
`ask` on deploy keeps outward-facing actions behind a human.
Same `paths:` as the Claude rule. Body: "Review against
`.claude/rules/<name>.md`. MUST violations are errors. SHOULD violations are
warnings; accept a stated reason." One rule set then guides both writer and
reviewer.
If a regex, type, or lint rule can catch it, write the check and cut the rule to one line that points at it. Keep prose for judgment calls.
| Rule shape | Mechanism |
|---|---|
| "X must not import Y" | Lint import restriction ( `no-restricted-imports` ,`import-linter` ,`depguard` ) |
| "Never use construct Z" | Lint rule, built-in or local |
| "Never edit generated files" | `deny` in settings, plus regenerate then`git diff --exit-code` in CI |
| "Generator must be current" | Hash inputs into a committed manifest; fail with "run `X` first" |
| "No hardcoded ids or secrets" | Small `verify:*` script that prints file:line |
| "Format and types before commit" | Pre-commit hook |
| "Always do X after an edit" | Claude `PostToolUse` hook in`.claude/settings.json` |
| "Ask before deploy" | `ask` permission |
**Pre-commit hook: fix, then gate.** First run fixers on staged files only and
re-stage (`lint-staged`, `pre-commit`, `lefthook`). Then run the read-only
`check-all`. Skip it under `CI`. Add a rule line: don't re-run these by hand
before a commit, and never pass `--no-verify`.
**Write the why into the check.** Open each custom check with a comment: what
it catches, the incident behind it, how to fix a failure. Claude reads the file
before editing it, so the stakes travel with the code.
**Keep checks fast.** Claude checks often only when checking is cheap. Use
incremental type-check, checksum-skipped generators, diff-gated sub-checks
(read scope from the real configs, not a second glob list), capped test workers
under `CI`, and counts instead of per-file logs.
Run from the repo root. Fix every ERROR before you report.
``` python
python3 - <<'PY'
import glob, json, os, re, subprocess
files = subprocess.run(["git", "ls-files"], capture_output=True, text=True).stdout.split()
def rx(g):
s = re.escape(g).replace(r"\*\*/", "(?:.*/)?").replace(r"\*\*", ".*").replace(r"\*", "[^/]*")
s = re.sub(r"\\\{(.*?)\\\}", lambda m: "(?:" + m.group(1).replace(r"\,", ",").replace(",", "|") + ")", s)
return re.compile("^" + s + "$")
out = []
for r in sorted(glob.glob(".claude/rules/*.md")):
t = open(r).read(); fm = re.match(r"^---\n(.*?)\n---", t, re.S)
for g in re.findall(r"^\s*-\s*[\"']?([^\"'\n]+)", fm.group(1), re.M) if fm and "paths:" in fm.group(1) else []:
if not g.startswith("!") and not any(rx(g).match(f) for f in files):
out.append(f"ERROR {r}: glob '{g}' matches no tracked file")
if not r.endswith("README.md") and "## Requirements" not in t:
out.append(f"WARNING {r}: no Requirements section")
if os.path.exists("CLAUDE.md"):
t = open("CLAUDE.md").read(); n = t.count("\n")
if n > 150: out.append(f"WARNING CLAUDE.md is {n} lines (budget ~150)")
if os.path.exists("package.json"):
s = json.load(open("package.json")).get("scripts", {})
for c in sorted(set(re.findall(r"\b(?:pnpm|npm|yarn|bun) run ([\w:.-]+)", t)) - set(s)):
out.append(f"ERROR CLAUDE.md names '{c}', not a package.json script")
else: out.append("ERROR no root CLAUDE.md")
if ".claude/settings.local.json" in files: out.append("ERROR settings.local.json is tracked; git-ignore it")
if ".claude/settings.json" not in files: out.append("WARNING no shared .claude/settings.json")
sec = re.compile(r"hooks\.slack\.com/services/|webhook\.office\.com|powerautomate/automations|[?&]sig=[\w%-]{20,}|ghp_\w{30,}|xox[bap]-[\w-]{10,}|AKIA[0-9A-Z]{16}|BEGIN [A-Z ]*PRIVATE KEY")
for f in files:
if f.startswith((".claude/", ".github/", ".argus/")) or f in ("CLAUDE.md", "AGENTS.md"):
if os.path.isfile(f) and sec.search(open(f, errors="ignore").read()):
out.append(f"ERROR {f}: looks like a committed secret or signed webhook")
print("\n".join(out) or "clean")
PY
Then run every command you wrote into CLAUDE.md once, or say plainly which
ones you didn't run and why.
| Mistake | Instead |
|---|---|
One giant CLAUDE.md , or pasting framework docs into it |
Traps and pointers only; docs stay in docs/ |
The same requirement in CLAUDE.md , a rule, and a subtree file |
One home: the rule |
| Rule globs guessed from memory | Globs copied from real paths, then verified |
| Commands documented but never run | Run them, or list them as unverified |
| Fixing broken scripts nobody asked about | Document them as known failures |
| Deleting a strange-looking flag | Ask why it exists; document it if it matters |
| Prose rules for things a linter can catch | Write the check |
| Ten layers of setup for a tiny repo | Stop at the level the repo needs |
| Personal allowlist committed as team settings | Team: settings.json . Personal:settings.local.json , git-ignored |
When Claude makes the same wrong guess twice, add one line to CLAUDE.md or,
better, a check. When a rule and the code disagree, the code wins, so fix the
rule the same day. Counts and file names drift first, so recheck them after
reorgs.