Bootstrap a best-practice Claude Code configuration for a new or unconfigured project. Use this skill when a user asks to set up Claude Code, initialize a project, create a CLAUDE.md, or configure permissions/hooks/settings for the first time. Also use when the user says things like "set up this project", "configure Claude Code", "bootstrap config", or "better /init". This skill replaces the built-in /init with a leaner, more opinionated setup grounded in current best practices.
You are setting up a Claude Code configuration from scratch. The project directory may be empty or nearly empty. Your goal is to create a lean, high-quality baseline that works for any project regardless of language, framework, or tooling.
Every line in CLAUDE.md costs context tokens on every single message. Frontier models follow ~150–200 instructions reliably; the system prompt already uses ~50. That leaves ~100–150 slots before quality degrades across all rules. Configuration is a multiplier on everything Claude Code does — invest in it upfront, keep it lean, let automation compound.
The single most impactful principle: give Claude a way to verify its work. If Claude has a feedback loop (tests, linters, type checkers), output quality doubles or triples.
Before creating any files, understand what you're working with.
CLAUDE.md, AGENTS.md, .claude/, .mcp.json. If any exist, tell the user this skill is for fresh setups and suggest using instead./cc-optimizepackage.json, composer.json, Cargo.toml, pyproject.toml, go.mod, Makefile, Gemfile, README.md, pom.xml, build.gradle, any *.sln or *.csproj files. Note what you find..eslintrc*, .prettierrc*, phpcs.xml*, rustfmt.toml, .editorconfig, CI configs (.github/workflows/, .gitlab-ci.yml), pre-commit configs..env, .env.*, secrets/, any *credentials* or *secret* files.If the project directory is truly empty or has minimal content, ask the user:
If $ARGUMENTS was provided, use that as the project description and infer what you can. Only ask about things you genuinely cannot determine.
This file provides permissions, hooks, and environment variables. It goes into version control.
Build it from these components:
Always include permissions.deny for sensitive files. Adapt the patterns to what you found in Step 1:
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"permissions": {
"deny": [
"Read(./.env)",
"Read(./.env.*)",
"Read(./secrets/**)",
"Bash(curl:*)",
"Bash(wget:*)",
"Bash(rm -rf:*)"
]
}
}
Adjust deny rules based on what you found:
For permissions.allow, add entries only if you can identify concrete, safe commands from the project (e.g., Bash(npm run test:*), Bash(cargo test:*)). If the project is too empty to know, leave allow out — the user will add it interactively and can persist choices via /permissions.
If you identified a formatter in Step 1, add a PostToolUse hook:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "<formatter-command> || true"
}
]
}
]
}
}
Common formatter commands by ecosystem:
jq -r '.tool_input.file_path' | xargs npx prettier --writejq -r '.tool_input.file_path' | xargs php-cs-fixer fixjq -r '.tool_input.file_path' | xargs rustfmtjq -r '.tool_input.file_path' | xargs ruff formatjq -r '.tool_input.file_path' | xargs gofmt -wIf no formatter is detected, skip the hook — don't guess. Note it in the summary for the user to add later.
The || true suffix is mandatory. Hooks must never crash Claude Code.
Always include these cost-optimization defaults:
{
"env": {
"CLAUDE_AUTOCOMPACT_PCT_OVERRIDE": "50"
}
}
This overrides auto-compaction from the default ~83% (too late for good quality) to 50%.
Build a project-level CLAUDE.md. Target 20–40 lines for a fresh project. It will grow as the project grows.
Structure:
# <Project Name>
<One-line description. Stack summary.>
## Commands
<List exact build/test/lint/dev commands. If unknown yet, add placeholders with TODO markers.>
## Structure
<Only if you can already identify a meaningful directory layout. Otherwise omit.>
## Conventions
<Only concrete rules that deviate from language defaults or that Claude commonly gets wrong. If the project is too new, keep this minimal or omit.>
## Don't
<Explicit prohibitions. Always include at minimum:>
- Don't commit secrets or credentials to git
- Don't use --force flags — fix the underlying issue instead
## Learnings
When the user corrects a mistake or points out a recurring issue, append a one-line
summary to .claude/learnings.md. Don't modify CLAUDE.md directly.
## Compact Instructions
When compacting, preserve: list of modified files, current test status, open TODOs, and key decisions made.
Rules for writing CLAUDE.md:
IMPORTANT: or YOU MUST sparingly — if everything is important, nothing is.npm test -- auth.test.ts" beats "run the relevant tests").Only create this if you have evidence that other AI coding tools are used (e.g., .codex/, .gemini/, .github/copilot/, cursor-related configs, or the user mentions it).
AGENTS.md is the vendor-neutral standard read by Codex, Amp, Cursor, Copilot, and others. It demonstrably reduces runtime (~29%) and output token consumption (~17%).
If created, keep it focused on universal concerns: setup commands, architecture boundaries, code style rules, testing conventions, and safety rules. Then reference it from CLAUDE.md via @AGENTS.md.
Append these lines if they're not already present:
# Claude Code — personal files
.claude/settings.local.json
.claude/local.md
Add a "Key Config Files" table to CLAUDE.md and a pre-commit hook that keeps it in sync with the filesystem. This gives Claude instant orientation on every message without manual maintenance.
After the project description line, add a ## Key Config Files section with a Markdown table listing every config file you created in the previous steps. Use this format:
## Key Config Files
| File | Purpose |
| ----------------------- | ------------------------------------------ |
| `CLAUDE.md` | Project instructions, loaded every message |
| `.claude/settings.json` | Permissions, hooks, environment variables |
| `.gitignore` | Git ignore patterns |
Include only files that actually exist and are tracked by git (not gitignored). Write a concise, specific purpose for each.
Create scripts/sync-config-table.sh — a bash script that automatically keeps the table in sync:
#!/usr/bin/env bash
# Keeps the "Key Config Files" table in CLAUDE.md in sync with the filesystem.
# - Removes rows for files that no longer exist
# - Appends rows for new config files with a placeholder description
# - Excludes gitignored files (they are per-machine, not part of the committed state)
# Preserves all existing hand-written descriptions.
# Invoked automatically by the pre-commit hook.
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
CLAUDE_MD="$ROOT/CLAUDE.md"
if [[ ! -f "$CLAUDE_MD" ]]; then
echo "sync-config-table: CLAUDE.md not found, skipping"
exit 0
fi
# Collect config files
config_files=()
# Root-level config files (by extension)
while IFS= read -r -d '' f; do
name="$(basename "$f")"
# Skip non-config files
case "$name" in
package-lock.json|README.md|CHANGELOG.md|AGENTS.md|CLAUDE.md|LICENSE) continue ;;
esac
config_files+=("$name")
done < <(find "$ROOT" -maxdepth 1 -type f \( -name '*.json' -o -name '*.js' -o -name '*.ts' -o -name '*.mjs' -o -name '*.cjs' -o -name '*.yaml' -o -name '*.yml' \) -print0 2>/dev/null | sort -z)
# Root-level dotfiles that are config files
for dotfile in .gitignore .npmignore .prettierignore .editorconfig .nvmrc .node-version; do
[[ -f "$ROOT/$dotfile" ]] && config_files+=("$dotfile")
done
# .claude/ direct children (skip subdirectories like skills/)
if [[ -d "$ROOT/.claude" ]]; then
while IFS= read -r -d '' f; do
config_files+=(".claude/$(basename "$f")")
done < <(find "$ROOT/.claude" -maxdepth 1 -type f -print0 2>/dev/null | sort -z)
fi
# .claude/skills/ skill definitions
if [[ -d "$ROOT/.claude/skills" ]]; then
while IFS= read -r -d '' f; do
relpath="${f#$ROOT/}"
config_files+=("$relpath")
done < <(find "$ROOT/.claude/skills" -maxdepth 2 -name 'SKILL.md' -type f -print0 2>/dev/null | sort -z)
fi
# .github/workflows/
if [[ -d "$ROOT/.github/workflows" ]]; then
while IFS= read -r -d '' f; do
config_files+=(".github/workflows/$(basename "$f")")
done < <(find "$ROOT/.github/workflows" -maxdepth 1 -type f -print0 2>/dev/null | sort -z)
fi
# Filter out gitignored files (per-machine / personal files don't belong
# in the committed config table — they may not exist on other clones).
# git check-ignore exits 0 if the path is ignored, 1 if tracked/untracked-but-not-ignored.
filtered_files=()
cd "$ROOT"
for file in "${config_files[@]}"; do
if ! git check-ignore -q "$file" 2>/dev/null; then
filtered_files+=("$file")
fi
done
config_files=("${filtered_files[@]}")
# Sort config files
IFS=$'\n' sorted_files=($(sort <<<"${config_files[*]}")); unset IFS
# Parse existing descriptions from CLAUDE.md
declare -A descriptions
section_found=false
while IFS= read -r line; do
if [[ "$line" == *"## Key Config Files"* ]]; then
section_found=true
continue
fi
if $section_found; then
if [[ "$line" =~ ^\|[[:space:]]*\`([^\`]+)\`[[:space:]]*\|[[:space:]]*(.+)[[:space:]]*\| ]]; then
file="${BASH_REMATCH[1]}"
desc="${BASH_REMATCH[2]}"
[[ "$file" == "File" ]] && continue
descriptions["$file"]="$desc"
fi
fi
done < "$CLAUDE_MD"
# Build new table
new_table="| File | Purpose |
|------|---------|"
for file in "${sorted_files[@]}"; do
desc="${descriptions[$file]:-TODO: add description}"
new_table+=$'\n'"| \`$file\` | $desc |"
done
# Replace the table in CLAUDE.md
# Find the section, skip old blank lines + table rows, emit new table
tmpfile="$(mktemp)"
in_section=false
table_replaced=false
while IFS= read -r line; do
if [[ "$line" == *"## Key Config Files"* ]]; then
in_section=true
echo "$line" >> "$tmpfile"
continue
fi
if $in_section && ! $table_replaced; then
# Skip blank lines and old table rows between heading and next content
if [[ "$line" == "" ]] || [[ "$line" == "|"* ]]; then
continue
fi
# First non-blank, non-table line: emit new table, then this line
echo "" >> "$tmpfile"
echo "$new_table" >> "$tmpfile"
echo "" >> "$tmpfile"
echo "$line" >> "$tmpfile"
table_replaced=true
in_section=false
continue
fi
echo "$line" >> "$tmpfile"
done < "$CLAUDE_MD"
# If we hit EOF while still in the section (table is the last thing)
if $in_section && ! $table_replaced; then
echo "" >> "$tmpfile"
echo "$new_table" >> "$tmpfile"
fi
# Check for changes
if diff -q "$CLAUDE_MD" "$tmpfile" > /dev/null 2>&1; then
echo "sync-config-table: no changes"
rm "$tmpfile"
else
mv "$tmpfile" "$CLAUDE_MD"
echo "sync-config-table: updated CLAUDE.md"
git add CLAUDE.md
fi
Make the script executable: chmod +x scripts/sync-config-table.sh
Create .githooks/pre-commit:
#!/usr/bin/env bash
# Keep CLAUDE.md config file table in sync
bash scripts/sync-config-table.sh
Make it executable: chmod +x .githooks/pre-commit
Run this command to tell git to use .githooks/ instead of the default .git/hooks/:
git config core.hooksPath .githooks
This needs to be run once per clone. Note this in the summary (Step 7) so the user is aware.
Important: If the project already uses Husky or another hook manager, skip this entire step and note it in the summary. The sync script would conflict with existing hook infrastructure.
After creating all files, give the user a concise summary:
/cc-optimize after the project has some code to get a project-aware configuration pass..mcp.json as needs arise (Context7 for docs, GitHub for PRs, etc.).git config core.hooksPath .githooks.claude/learnings.md instead of modifying CLAUDE.md directly./cc-optimize reviews it and proposes promoting recurring patterns into CLAUDE.md or skills, and deleting one-off entries./init. This skill replaces it..claude/local.md or settings.local.json. Those are personal and should be created by the user.