{"slug": "audit-or-set-up-a-code-repository-so-claude-code-works-efficiently-in-it", "title": "Audit or set up a code repository so Claude Code works efficiently in it", "summary": "A developer published a guide for auditing or scaffolding a code repository so that Claude Code works efficiently within it, covering CLAUDE.md files, path-scoped .claude/rules, subtree memory, verify scripts, pre-commit hooks, shared settings.json, agents and skills. The guide defines three principles — Claude should load only what a task needs, every requirement should have a single home, and code should enforce what matters — and lays out a six-step process of inventory, scoring, planning, scaffolding, verification and reporting. It also defines a two-level maturity checklist, with Level 1 covering runnable project commands and Level 2 covering scoped context such as a root CLAUDE.md under roughly 150 lines and path-globbed rules.", "body_md": "| name | setting-up-claude-ready-repos | \n|---|---|\n| 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\". | \n\nA repo works well with Claude when three things hold:\n\n1. **Claude loads only what the task needs.** Small, scoped instructions beat\none big file. Every always-loaded line costs context in every session.\n2. **Every requirement has one home.** Two copies drift, and Claude can't tell\nwhich is right.\n3. **Code enforces what matters.** A failing check beats a sentence Claude\nmight skim past.\n\nEverything below serves those three.\n\n1. **Inventory** the repo (commands below). Record every trap with evidence.\n2. **Score** it against the checklist. Report the level, gaps, and a plan.\n3. **Stop and show the plan.** You're about to add files that shape every\nfuture session. If the user said \"just do it\", keep the plan short and go on\nunless they object.\n4. **Scaffold** , smallest layer first, from the templates.\n5. **Verify** with the checks at the end. Fix what they flag.\n6. **Report** : what you created, what you left alone, what the user must\ndecide, and each trap with its evidence.\n\nEverything 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.\n\nRun from the repo root. Read-only.\n\n```\nls package.json pnpm-workspace.yaml pyproject.toml go.mod Cargo.toml pom.xml \\\n   build.gradle* Makefile .nvmrc .tool-versions .python-version .java-version 2>/dev/null\nnode -e 'const p=require(\"./package.json\");console.log(p.scripts,p.packageManager)' 2>/dev/null\ngrep -nE '\"[~^][0-9]' package.json                        # unpinned versions\ngit ls-files | grep -E '(^|/)(tsconfig[^/]*\\.json|jest\\.config\\.|vitest\\.config\\.|pytest\\.ini|package\\.json|pyproject\\.toml)'\ngit grep -lIiE 'auto-?generated|do not edit|generated by' | head -20\ngit grep -nIiE 'must (stay|not|run)|do not (remove|change)|load-bearing|keep this' \\\n  -- '*.json' '*.js' '*.mjs' '*.sh' '*.yml' '*.yaml' '*.toml' Makefile | head -20\nls CLAUDE.md AGENTS.md .claude/settings.json .claude/settings.local.json 2>/dev/null\ngit ls-files | grep -E '(^|/)CLAUDE\\.md$|^\\.claude/'\nls .husky .pre-commit-config.yaml lefthook.yml .eslintrc* eslint.config.* 2>/dev/null\n```\n\nThen read what it points at. Look for what a model would guess wrong:\n\n- Commands with non-obvious flags, ordering, or look-alike names.\n- Settings whose removal breaks something far away.\n- Generated files, and the command that regenerates them.\n- Folders with their own tsconfig, test runner, or language.\n- Import boundaries between layers or packages.\n- Scripts that are broken today. Note them as known failures. Don't fix them unasked.\n\nMark 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.\n\n**Level 1: Claude can run the project**\n\n- Root `CLAUDE.md` written by a person, not raw`/init` output.\n- Every command in `CLAUDE.md` exists in the package scripts or Makefile.\n- Named scripts for build, test, lint, type-check, plus one \"check everything\" and one \"fix everything\".\n- Toolchain pinned (`.nvmrc` ,`packageManager` ,`.python-version` , ...).\n- Generated paths named, with the regenerate command.\n- Load-bearing settings named, with what breaks without them.\n\n**Level 2: context is scoped**\n\n- Root `CLAUDE.md` under ~150 lines, no pasted docs.\n- `.claude/rules/*.md` with a`paths:` glob on every rule that isn't truly\nrepo-wide. Every glob matches at least one file.\n- Each rule has Requirements and Where to Look, and no large code blocks.\n- `.claude/rules/README.md` indexes every rule with its glob.\n- Subtree `CLAUDE.md` wherever the toolchain changes, and nowhere else.\n- No requirement stated in two places.\n- A doc map reaches every doc.\n\n**Level 3: key rules are enforced**\n\n- Layer boundaries enforced by lint import restrictions.\n- Generated output checked in the build.\n- Pre-commit hook: auto-fix staged files, then run the full check.\n- Each custom check opens with a comment naming what it prevents.\n- Dependency versions pinned exactly, or a stated reason why not.\n\n**Level 4: the loop is fast and shared**\n\n- Incremental type-check. Slow generators skip when inputs are unchanged.\n- The full check skips what the diff can't affect, or runs in under a minute.\n- `.claude/settings.json` committed: read-only allowlist, generated paths\ndenied for`Edit` /`Write` , deploy and publish set to`ask` .\n- Agents for deep domain knowledge. Skills for repeated multi-step chores.\nSkills that install or change the environment set\n`disable-model-invocation: true` .\n\n**Level 5: writer and reviewer agree**\n\n- A PR review bot reads the same rules (thin wrappers, same globs).\n- PR title, commit, and evidence rules exist, with the reason for each.\n- Known pre-existing failures are listed, so Claude doesn't chase them.\n\n**Always flag, at any level**\n\n- Secrets, webhook URLs, signed links, or tokens in committed config.\n- Counts or file names in `CLAUDE.md` that no longer match the tree.\n- Rules that contradict the code. The code wins, so fix the rule.\n- Commands in docs that fail when run.\n\nEach step is useful alone. Stop where the repo's size says to stop.\n\n1. **Named scripts.** Add \"check everything\" and \"fix everything\" if missing.\nPoint out unpinned versions. Don't change a version without asking.\n2. **Root `CLAUDE.md`.** Traps and pointers, not a tour. If one exists, it's\nthe user's work: restructure it, keep every fact that's still true, move\nrequirements into rules, and show what moved where.\n3. **Path-scoped rules** plus the rules README.\n4. **Subtree `CLAUDE.md`** , only at toolchain boundaries.\n5. **Enforcement.** Turn checkable rules into checks. Add the pre-commit hook.\n6. **Shared `.claude/settings.json`.** Personal entries stay in` settings.local.json` , which must be git-ignored.\n7. **Optional:** agents, skills, review-bot wrappers.\n\n**Small repos:** a single package may need only a root `CLAUDE.md`, two or\nthree rules, a hook, and `settings.json`. Don't build ten layers for 500 lines\nof code.\n\n**Unknown intent:** when you can't tell why a setting exists, list it as a\nquestion for the user. Don't mark it load-bearing and don't delete it.\n\nReplace every `<...>`. Delete sections with nothing true to say.\n\n```\n# <app name>\n\n<One or two sentences: what it is, and the one structural fact that surprises\nnewcomers (monorepo? two stacks? generated code?).>\n\n## Essential commands\n\n``` bash\n<pm> run build          # <what it produces; any required step before it>\n<pm> run test           # <which runners, over which folders>\n<pm> run check-all      # read-only: format, lint, types, verify:*\n<pm> run check-all:fix\n```\n\nUse the named script. Don't split `check-all` into hand-run tools.\n\n## Traps\n\n- **`<flag>` is load-bearing.** <What breaks without it.> Do not clean it up.\n- **`<cmd A>` is not `<cmd B>`.** <What each does.> Use <A> for <case>.\n- **<Error> looks like <cause X>.** The real cause is <Y>. Fix: <Z>.\n\n## Always-true patterns\n\n- Never hand-edit `<generated path>`. Change `<source>`, run `<pm> run <gen>`.\n- <Banned construct, and what to use instead.>\n\n## Known failures\n\n<Pre-existing breaks, so Claude doesn't chase them.>\n\n## Where things are\n\nRules: `.claude/rules/README.md` (they load by the path you edit).\nDocs: `<docs/README.md>`. Templates: `<docs/templates/>`.\n---\npaths:\n  - \"<subtree>/**/*.<ext>\"\n---\n\n# <Concern>\n\n## Requirements\n\n- MUST <requirement>. <Reason, if not obvious.>\n- MUST NOT <anti-pattern>. Enforced by `<lint rule or verify script>`.\n- SHOULD <default>. <When deviating is fine.>\n\n## Where to Look\n\n- `<templates/x-template.ext>` - canonical structure\n- `<src/real/example.ext>` - a real, current example\n```\n\nRules hold requirements and pointers. Examples live in docs and real code, so\nrules stay short and the examples stay correct. Scope every glob to one\nsubtree, so a UI rule never loads for server work. Only rules that apply to\neverything (PR format, repo hygiene) go without `paths:`.\n\n```\n# Rules\n\nRules load by their `paths:` glob when Claude edits a matching file. Rules\nwithout `paths:` load every session, so keep those few.\n\n| Rule | Loads for |\n|---|---|\n| `<name>.md` | `<glob>` |\n| `<pull-requests>.md` | always |\n```\n\nA glob can't say \"this folder uses a different test runner.\" A subtree file can. It carries orientation only. It never restates a requirement.\n\n```\n# <subtree> - <stack summary>\n\n| | |\n|---|---|\n| Type-check | `<pm> run type-check:<name>` (`<tsconfig>`) |\n| Test | `<pm> run test:<name>` (<runner>) |\n| One test | `<pm> exec <runner> run <path/one.spec.ts>` |\n\n**Boundary:** <the only public entry point; what outside code must not\nimport; the lint rule that enforces it>.\n\n**Rules that fire here:** <rule files whose globs match this tree>.\n{\n  \"permissions\": {\n    \"allow\": [\"Bash(git status)\", \"Bash(git diff:*)\", \"Bash(git log:*)\",\n              \"Bash(<pm> run test:*)\", \"Bash(<pm> run check-all)\"],\n    \"ask\":   [\"Bash(<pm> run deploy:*)\", \"Bash(<pm> publish:*)\", \"Bash(git push:*)\"],\n    \"deny\":  [\"Edit(<generated path>/**)\", \"Write(<generated path>/**)\"]\n  }\n}\n```\n\n`deny` on generated paths stops edits at the harness, not by convention.\n`ask` on deploy keeps outward-facing actions behind a human.\n\nSame `paths:` as the Claude rule. Body: \"Review against\n`.claude/rules/<name>.md`. MUST violations are errors. SHOULD violations are\nwarnings; accept a stated reason.\" One rule set then guides both writer and\nreviewer.\n\nIf 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.\n\n| Rule shape | Mechanism | \n|---|---|\n| \"X must not import Y\" | Lint import restriction ( `no-restricted-imports` ,`import-linter` ,`depguard` ) | \n| \"Never use construct Z\" | Lint rule, built-in or local | \n| \"Never edit generated files\" | `deny` in settings, plus regenerate then`git diff --exit-code` in CI | \n| \"Generator must be current\" | Hash inputs into a committed manifest; fail with \"run `X` first\" | \n| \"No hardcoded ids or secrets\" | Small `verify:*` script that prints file:line | \n| \"Format and types before commit\" | Pre-commit hook | \n| \"Always do X after an edit\" | Claude `PostToolUse` hook in`.claude/settings.json` | \n| \"Ask before deploy\" | `ask` permission | \n\n**Pre-commit hook: fix, then gate.** First run fixers on staged files only and\nre-stage (`lint-staged`, `pre-commit`, `lefthook`). Then run the read-only\n`check-all`. Skip it under `CI`. Add a rule line: don't re-run these by hand\nbefore a commit, and never pass `--no-verify`.\n\n**Write the why into the check.** Open each custom check with a comment: what\nit catches, the incident behind it, how to fix a failure. Claude reads the file\nbefore editing it, so the stakes travel with the code.\n\n**Keep checks fast.** Claude checks often only when checking is cheap. Use\nincremental type-check, checksum-skipped generators, diff-gated sub-checks\n(read scope from the real configs, not a second glob list), capped test workers\nunder `CI`, and counts instead of per-file logs.\n\nRun from the repo root. Fix every ERROR before you report.\n\n``` python\npython3 - <<'PY'\nimport glob, json, os, re, subprocess\nfiles = subprocess.run([\"git\", \"ls-files\"], capture_output=True, text=True).stdout.split()\ndef rx(g):\n    s = re.escape(g).replace(r\"\\*\\*/\", \"(?:.*/)?\").replace(r\"\\*\\*\", \".*\").replace(r\"\\*\", \"[^/]*\")\n    s = re.sub(r\"\\\\\\{(.*?)\\\\\\}\", lambda m: \"(?:\" + m.group(1).replace(r\"\\,\", \",\").replace(\",\", \"|\") + \")\", s)\n    return re.compile(\"^\" + s + \"$\")\nout = []\nfor r in sorted(glob.glob(\".claude/rules/*.md\")):\n    t = open(r).read(); fm = re.match(r\"^---\\n(.*?)\\n---\", t, re.S)\n    for g in re.findall(r\"^\\s*-\\s*[\\\"']?([^\\\"'\\n]+)\", fm.group(1), re.M) if fm and \"paths:\" in fm.group(1) else []:\n        if not g.startswith(\"!\") and not any(rx(g).match(f) for f in files):\n            out.append(f\"ERROR   {r}: glob '{g}' matches no tracked file\")\n    if not r.endswith(\"README.md\") and \"## Requirements\" not in t:\n        out.append(f\"WARNING {r}: no Requirements section\")\nif os.path.exists(\"CLAUDE.md\"):\n    t = open(\"CLAUDE.md\").read(); n = t.count(\"\\n\")\n    if n > 150: out.append(f\"WARNING CLAUDE.md is {n} lines (budget ~150)\")\n    if os.path.exists(\"package.json\"):\n        s = json.load(open(\"package.json\")).get(\"scripts\", {})\n        for c in sorted(set(re.findall(r\"\\b(?:pnpm|npm|yarn|bun) run ([\\w:.-]+)\", t)) - set(s)):\n            out.append(f\"ERROR   CLAUDE.md names '{c}', not a package.json script\")\nelse: out.append(\"ERROR   no root CLAUDE.md\")\nif \".claude/settings.local.json\" in files: out.append(\"ERROR   settings.local.json is tracked; git-ignore it\")\nif \".claude/settings.json\" not in files: out.append(\"WARNING no shared .claude/settings.json\")\nsec = 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\")\nfor f in files:\n    if f.startswith((\".claude/\", \".github/\", \".argus/\")) or f in (\"CLAUDE.md\", \"AGENTS.md\"):\n        if os.path.isfile(f) and sec.search(open(f, errors=\"ignore\").read()):\n            out.append(f\"ERROR   {f}: looks like a committed secret or signed webhook\")\nprint(\"\\n\".join(out) or \"clean\")\nPY\n```\n\nThen run every command you wrote into `CLAUDE.md` once, or say plainly which\nones you didn't run and why.\n\n| Mistake | Instead | \n|---|---|\n| One giant `CLAUDE.md` , or pasting framework docs into it | Traps and pointers only; docs stay in `docs/` | \n| The same requirement in `CLAUDE.md` , a rule, and a subtree file | One home: the rule | \n| Rule globs guessed from memory | Globs copied from real paths, then verified | \n| Commands documented but never run | Run them, or list them as unverified | \n| Fixing broken scripts nobody asked about | Document them as known failures | \n| Deleting a strange-looking flag | Ask why it exists; document it if it matters | \n| Prose rules for things a linter can catch | Write the check | \n| Ten layers of setup for a tiny repo | Stop at the level the repo needs | \n| Personal allowlist committed as team settings | Team: `settings.json` . Personal:`settings.local.json` , git-ignored | \n\nWhen Claude makes the same wrong guess twice, add one line to `CLAUDE.md` or,\nbetter, a check. When a rule and the code disagree, the code wins, so fix the\nrule the same day. Counts and file names drift first, so recheck them after\nreorgs.", "url": "https://wpnews.pro/news/audit-or-set-up-a-code-repository-so-claude-code-works-efficiently-in-it", "canonical_source": "https://gist.github.com/valarpirai/b1d0f98ef9519e2802701ab28acc2935", "published_at": "2026-09-25 10:38:13+00:00", "updated_at": "2026-09-25 11:01:54.571648+00:00", "lang": "en", "topics": ["ai-tools", "developer-tools", "ai-agents", "ai-products"], "entities": ["Claude Code", "Anthropic"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/audit-or-set-up-a-code-repository-so-claude-code-works-efficiently-in-it", "markdown": "https://wpnews.pro/news/audit-or-set-up-a-code-repository-so-claude-code-works-efficiently-in-it.md", "text": "https://wpnews.pro/news/audit-or-set-up-a-code-repository-so-claude-code-works-efficiently-in-it.txt", "jsonld": "https://wpnews.pro/news/audit-or-set-up-a-code-repository-so-claude-code-works-efficiently-in-it.jsonld"}}