From cc-config
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.
How this skill is triggered — by the user, by Claude, or both
Slash command
/cc-config:cc-config-init [optional: brief project description][optional: brief project description]This skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
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.
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.
If .claude/learnings.md exists, read all entries and apply them silently to inform this run. The [skill-name] tag on each entry is provenance only — all entries apply regardless of which skill wrote them. Do not announce that learnings were loaded.
If the file does not exist, proceed without mention.
Before creating any files, understand what you're working with.
CLAUDE.md, AGENTS.md, .claude/, .mcp.json, context/. If any exist, tell the user this skill is for fresh setups and suggest using /cc-config-optimize instead.package.json, composer.json, Cargo.toml, pyproject.toml, go.mod, Makefile, Gemfile, pom.xml, build.gradle, any *.sln or *.csproj files.hugo.toml, config.toml, config.yaml (Hugo), _config.yml (Jekyll), astro.config.*, .eleventy.js, mkdocs.yml, content/, articles/, posts/, _posts/, dominant .md files, knowledge base or style guide files (STYLE.md, style-guide.md).README.md for purpose.DESIGN.md at the project root (open-source format — YAML design tokens + Markdown rationale; Claude Code and other agents read it automatically). Also check context/design/ for Claude Design handoff artifacts (PROMPT.md, design-notes.md, screenshots/)..eslintrc*, .prettierrc*, phpcs.xml*, rustfmt.toml, .editorconfig, CI configs (.github/workflows/, .gitlab-ci.yml), pre-commit configs..vale.ini / vale.ini, .markdownlint.{json,yaml,yml}, prettier configured for Markdown..env, .env.*, secrets/, any *credentials* or *secret* files..github/ exists or the git remote points to GitHub. Note the result — used in Step 4a.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.
Regardless of project size, also ask:
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 -wjq -r '.tool_input.file_path' | xargs npx prettier --write or jq -r '.tool_input.file_path' | xargs markdownlint --fixVale is a prose linter, not a formatter — don't wire it into PostToolUse. If you want Vale to run, suggest it as a manual command in CLAUDE.md instead.
If 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%.
Context knowledge lives at three scope levels. Explain this to the user before creating anything:
Level 1 — Company/project scope (context/): Knowledge that applies to all work in this repo. Examples: company profile, brand voice, buyer personas, architecture decisions, API contracts. One file per distinct type of knowledge — name files after what they contain, not a fixed schema. Update one file, every skill that references it reflects the change.
Level 2 — Format scope (inside each skill's own folder): Knowledge specific to producing one output type — a whitepaper structure guide, blog length conventions, API endpoint style rules. Belongs with the skill that uses it, not in context/.
Level 3 — Campaign/feature scope (regular project subfolders): Briefings, campaign assets, or feature specs for a specific initiative. Scoped to that subfolder — not shared globally.
If the user confirmed company-level context in Step 1, ask: "What types of company-level context do you have?" Create one file per type they describe, with a TODO marker and a one-line description of what belongs there. Do not prescribe filenames — use names that reflect the actual content (company-profile.md, brand-voice.md, buyer-personas.md, architecture-decisions.md, or whatever fits the project).
Also create context/README.md as a brief index explaining the convention:
# Context
Company-scoped knowledge referenced by skills and CLAUDE.md files across this repo.
One file per type of knowledge — update once, every reference reflects the change.
Format-specific guidelines live inside each skill's own folder, not here.
Campaign or feature briefings live in their respective project subfolders, not here.
Register each context file in the ## Context files table in CLAUDE.md (see Step 3).
Hierarchical CLAUDE.md for multi-level projects: If the project has a nested folder structure — a marketing monorepo with campaign subfolders, a multi-package code repo, a website with distinct content sections — suggest CLAUDE.md files at each meaningful directory level:
CLAUDE.md → ## Context files table listing all company-wide context files from context/CLAUDE.md → @-imports briefings or specs scoped to that folderWhen Claude Code starts in any subfolder, it reads CLAUDE.md files up the directory tree, so a session in campaigns/product-xy/june-2026/ automatically gets company context (via root CLAUDE.md) plus campaign context (via the local CLAUDE.md). Skills invoked in that session inherit all of it without hard-coded absolute paths to shared files. Guide the user to create and maintain CLAUDE.md files at each level where the context meaningfully changes as the project structure grows.
Claude Design handoffs: If the project uses Claude Design (Anthropic's visual design tool), direct the user to place Claude Design handoff artifacts (PROMPT.md, design-notes.md, screenshots/) in context/design/ — not the project root. This keeps handoff snapshots versioned alongside the codebase without cluttering the root. DESIGN.md is different: it is a persistent, project-wide design system spec (YAML tokens + Markdown rationale) that lives at the project root and is auto-read by Claude Code and other agents. If DESIGN.md already exists or is being added, wire it into CLAUDE.md with @DESIGN.md **Read when:** building or editing any UI component — do not copy its contents into context/.
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 or toolchain summary — works for code (e.g. "Next.js + Prisma") or content (e.g. "Hugo site, articles in Markdown, edited with Vale").>
## Commands
<List exact commands the project uses. Examples:
- Code projects: `npm test`, `cargo build`, `pytest tests/`
- Content/static-site projects: `hugo build`, `vale .`, `markdownlint **/*.md`, `pandoc input.md -o output.pdf`
If unknown yet, add placeholders with TODO markers.>
## Structure
<Only if you can already identify a meaningful directory layout. Otherwise omit. For content projects, mention things like the article output directory or where the knowledge base lives.>
## Context files
<Add this section only if a context/ folder was created in Step 2. Skills discover
and load these files on demand — they are not pre-loaded. One row per file, with a
short summary drawn from the file's content (10–20 words):>
| Category | File | Summary |
| ---------- | ------------------- | ----------------------- |
| <category> | `context/<name>.md` | TODO: brief description |
## References
<Optional. Use progressive disclosure for large reference docs that are not domain
context files: style guides, OpenSpec, architecture docs, DESIGN.md.
@style-guide.md **Read when:** writing or editing articles
@DESIGN.md **Read when:** building or editing any UI component
Only include this section if such files exist. Do not put context/ files here —
they belong in the Context files table above.>
## Conventions
<Only concrete rules that deviate from defaults or that Claude commonly gets wrong. For content projects this might be voice/tone, terminology, or output format requirements. For code projects, conventions that aren't enforced by the linter. 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.
If Step 1 detected that this is a GitHub-hosted project and .github/workflows/ does not already contain Claude Code action files, ask once:
"Would you like to add Claude Code GitHub Actions?
claude-code-review.yml— automatically reviews every pull request when it's opened or updatedclaude.yml— lets anyone trigger Claude by mentioning@claudein issues, PR comments, or PR reviewsBoth require a
CLAUDE_CODE_OAUTH_TOKENsecret set in your GitHub repo settings. Add one, both, or skip."
github-actions/claude-code-review.yml and github-actions/claude.yml (in this skill's directory) and write them to .github/workflows/. Create the .github/workflows/ directory if it does not exist.After writing, do not add the workflow files to the Key Config Files table in Step 6 — the sync script already picks up .github/workflows/ entries automatically.
Note in Step 7 if any workflow files were added: the user must add CLAUDE_CODE_OAUTH_TOKEN as a repository secret in GitHub Settings → Secrets and variables → Actions before the workflows will run.
Append these lines if they're not already present:
# Claude Code — personal files
.claude/settings.local.json
.claude/local.md
.claudeignore follows .gitignore syntax and tells Claude Code which paths to skip entirely when indexing the project. Every excluded directory reduces the invisible token overhead that accumulates before the user types anything.
Create .claudeignore if you detected any of the following in Step 1:
dist/, build/, .next/, out/, target/, _site/node_modules/, vendor/, .venv/, venv/coverage/, .nyc_output/Example for a typical JS/TS project:
node_modules/
dist/
.next/
coverage/
Adapt to what you actually found — don't create the file if the repo is small and tidy, and don't add entries speculatively. If the project is too empty to judge, note it in the Step 7 summary as something to add once the repo grows.
Add .claudeignore to the Key Config Files table in Step 6 if created.
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' -o -name '*.toml' \) -print0 2>/dev/null | sort -z)
# Root-level dotfiles that are config files
for dotfile in .gitignore .npmignore .prettierignore .editorconfig .nvmrc .node-version .vale.ini .markdownlint.json .markdownlint.yaml .markdownlint.yml; do
[[ -f "$ROOT/$dotfile" ]] && config_files+=("$dotfile")
done
# Root-level named config files (non-dotfile conventions)
if [[ -f "$ROOT/DESIGN.md" ]]; then
config_files+=("DESIGN.md")
fi
# .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
# context/ reference files
if [[ -d "$ROOT/context" ]]; then
while IFS= read -r -d '' f; do
relpath="${f#$ROOT/}"
config_files+=("$relpath")
done < <(find "$ROOT/context" -maxdepth 2 -type f -name '*.md' -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
mapfile -t sorted_files < <(printf '%s\n' "${config_files[@]}" | sort)
# 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"
# Auto-stage so the updated table is included in the triggering commit
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:
context/ files if they were scaffolded./context in a fresh session immediately after setup to check startup token overhead. If it exceeds ~10,000 tokens before sending a single message, something is loading too much — oversized CLAUDE.md, too many unconditional context imports, or a large number of MCP tools are common causes./cc-config-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.)./schedule skill can automate them — run a chain of skills on a cron schedule and land the output in a review folder for human sign-off before anything goes live.CLAUDE_CODE_OAUTH_TOKEN as a repository secret in GitHub Settings → Secrets and variables → Actions before the workflows will run.git config core.hooksPath .githooks.claude/learnings.md at the end of each run and recall them at the start of the next — no manual action required..claude/learnings.md instead of modifying CLAUDE.md directly./cc-config-optimize periodically reviews the file and proposes promoting recurring patterns into CLAUDE.md, skills, or hooks; one-off entries get deleted./init. This skill replaces it..claude/local.md or settings.local.json. Those are personal and should be created by the user.Auto-store phase. Before asking for feedback, review this run. For each qualifying observation, append one tagged line to .claude/learnings.md (create with standard header if missing):
[cc-config:cc-config-init] <concise fact about this project> — <YYYY-MM-DD>
Qualifies: something about this project that differs from what this skill assumes on a generic project; a suggestion the user explicitly accepted or rejected that deviates from skill defaults; a constraint or fact discovered that would change how this skill behaves next time.
Does not qualify: standard skill behavior applied without deviation; facts already present in CLAUDE.md, AGENTS.md, or other config files; anything a reader could determine from the repo without this skill having run; facts semantically equivalent to any existing .claude/learnings.md entry — when in doubt, skip.
Check for the file before appending:
ls .claude/learnings.md 2>/dev/null && echo "exists" || echo "missing"
Standard header when creating the file:
# Learnings
Corrections and observations collected during configuration sessions.
Entries are tagged by skill and dated.
---
Explicit feedback. After the auto-store phase, ask:
"Did this configuration meet your expectations? If anything needs adjusting, share it here — or press Enter to finish."
.claude/learnings.md.".claude/learnings.md." Then exit. If nothing was stored, skip the confirmation and exit directly.Note: Learnings are automatically recalled at the start of the next skill run. Run
/cc-config-optimizeperiodically to promote recurring patterns into the configuration.
Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub michaelvanlaar/cc-config --plugin cc-config