Skip to content

Instantly share code, notes, and snippets.

@HoangTienDuc
Last active May 11, 2026 16:16
Show Gist options
  • Select an option

  • Save HoangTienDuc/fa3be6d31b3e61605b4e6db1772184a7 to your computer and use it in GitHub Desktop.

Select an option

Save HoangTienDuc/fa3be6d31b3e61605b4e6db1772184a7 to your computer and use it in GitHub Desktop.
init claude in a repo
#!/usr/bin/env bash
# claude-init.sh — Bootstrap Claude Code workflow for any repository.
#
# Drop on PATH (e.g. ~/.local/bin/) and run from a project root.
# Idempotent: skips files already present unless --force is given.
#
# Design:
# 1. Auto-detect language/package manager → propose default commands.
# 2. INTERACTIVELY ASK the user for:
# - the docker container that is the dev environment
# - the workdir inside that container (path to the repo)
# - the actual commands they run (run, test, lint, format, typecheck)
# Auto-detected values are offered as defaults — press Enter to accept.
# 3. Generate scripts/dev-*.sh wrappers, a Makefile, and Claude Code config
# that funnel commands through `docker exec -w <workdir> <container>`.
#
# Usage:
# claude-init.sh [--container <name>] [--workdir <path>]
# [--no-docker] [--yes] [--force] [--dry-run]
# [--no-claude-md] [--help]
set -euo pipefail
SCRIPT_VERSION="2.0.0"
CLAUDE_DIR=".claude"
SCRIPTS_DIR="scripts"
# === CLI flags ===
EXPLICIT_CONTAINER=""
EXPLICIT_WORKDIR=""
NO_DOCKER=0
ASSUME_YES=0
FORCE=0
DRY_RUN=0
SKIP_CLAUDE_MD=0
# === Detected / chosen ===
PROJECT_NAME=""
LANGUAGE=""
PKG_MANAGER=""
HAS_DOCKER=0
DOCKER_CONTAINER=""
DOCKER_WORKDIR=""
RUN_CMD=""
TEST_CMD=""
LINT_CMD=""
FORMAT_CMD=""
TYPECHECK_CMD=""
PLUGINS_BLOCK='# Karpathy skills — practical patterns from Andrej Karpathy
/plugin marketplace add forrestchang/andrej-karpathy-skills
/plugin install andrej-karpathy-skills@karpathy-skills'
# === Logging ===
if [ -t 1 ]; then
C_R=$'\033[0;31m'; C_G=$'\033[0;32m'; C_Y=$'\033[0;33m'
C_B=$'\033[0;34m'; C_BD=$'\033[1m'; C_DM=$'\033[2m'; C_OFF=$'\033[0m'
else
C_R=""; C_G=""; C_Y=""; C_B=""; C_BD=""; C_DM=""; C_OFF=""
fi
log() { printf '%sℹ%s %s\n' "$C_B" "$C_OFF" "$*"; }
ok() { printf '%s✓%s %s\n' "$C_G" "$C_OFF" "$*"; }
warn() { printf '%s⚠%s %s\n' "$C_Y" "$C_OFF" "$*"; }
err() { printf '%s✗%s %s\n' "$C_R" "$C_OFF" "$*" >&2; }
step() { printf '\n%s==> %s%s\n' "$C_BD" "$*" "$C_OFF"; }
hint() { printf '%s %s%s\n' "$C_DM" "$*" "$C_OFF"; }
# === File helpers ===
write_file() {
local path="$1" content="$2"
if [ "$DRY_RUN" -eq 1 ]; then log "[dry-run] would write: $path"; return 0; fi
if [ -e "$path" ] && [ "$FORCE" -ne 1 ]; then warn "skip (exists): $path"; return 0; fi
mkdir -p "$(dirname "$path")"
printf '%s' "$content" > "$path"
ok "wrote: $path"
}
write_executable() {
write_file "$1" "$2"
if [ "$DRY_RUN" -ne 1 ] && [ -e "$1" ]; then chmod +x "$1"; fi
return 0
}
# prompt_with_default <prompt-text> <default-value> <var-name>
prompt_with_default() {
local prompt="$1" default="$2" var="$3" answer=""
if [ "$ASSUME_YES" -eq 1 ] || [ ! -t 0 ]; then
eval "$var=\"\$default\""
return 0
fi
if [ -n "$default" ]; then
printf ' %s [%s%s%s]: ' "$prompt" "$C_DM" "$default" "$C_OFF"
else
printf ' %s: ' "$prompt"
fi
IFS= read -r answer || answer=""
answer="${answer:-$default}"
eval "$var=\"\$answer\""
}
# === Detection (defaults only) ===
detect_project_name() {
if [ -d .git ] && command -v git >/dev/null 2>&1; then
PROJECT_NAME="$(basename "$(git rev-parse --show-toplevel 2>/dev/null || pwd)")"
fi
PROJECT_NAME="${PROJECT_NAME:-$(basename "$(pwd)")}"
}
detect_language() {
if [ -f package.json ]; then
if grep -q '"typescript"' package.json 2>/dev/null || ls tsconfig*.json >/dev/null 2>&1; then
LANGUAGE="typescript"
else
LANGUAGE="javascript"
fi
if [ -f pnpm-lock.yaml ]; then PKG_MANAGER="pnpm"
elif [ -f yarn.lock ]; then PKG_MANAGER="yarn"
elif [ -f bun.lockb ]; then PKG_MANAGER="bun"
else PKG_MANAGER="npm"; fi
elif [ -f pyproject.toml ] || [ -f requirements.txt ] || [ -f setup.py ]; then
LANGUAGE="python"
if [ -f uv.lock ]; then PKG_MANAGER="uv"
elif [ -f poetry.lock ]; then PKG_MANAGER="poetry"
elif [ -f Pipfile ]; then PKG_MANAGER="pipenv"
else PKG_MANAGER="pip"; fi
elif [ -f Cargo.toml ]; then LANGUAGE="rust"; PKG_MANAGER="cargo"
elif [ -f go.mod ]; then LANGUAGE="go"; PKG_MANAGER="go"
elif [ -f Gemfile ]; then LANGUAGE="ruby"; PKG_MANAGER="bundle"
elif [ -f composer.json ]; then LANGUAGE="php"; PKG_MANAGER="composer"
elif [ -f pom.xml ]; then LANGUAGE="java"; PKG_MANAGER="maven"
elif [ -f build.gradle ] || [ -f build.gradle.kts ]; then
LANGUAGE="java"; PKG_MANAGER="gradle"
else
LANGUAGE="unknown"; PKG_MANAGER="unknown"
fi
}
default_commands() {
case "$LANGUAGE" in
javascript|typescript)
TEST_CMD="$PKG_MANAGER test"
if grep -q '"lint"' package.json 2>/dev/null; then LINT_CMD="$PKG_MANAGER run lint"; fi
if grep -q '"format"' package.json 2>/dev/null; then FORMAT_CMD="$PKG_MANAGER run format"; fi
if grep -qE '"(typecheck|type-check|tsc)"' package.json 2>/dev/null; then
TYPECHECK_CMD="$PKG_MANAGER run typecheck"
fi
if grep -q '"dev"' package.json 2>/dev/null; then
RUN_CMD="$PKG_MANAGER run dev"
elif grep -q '"start"' package.json 2>/dev/null; then
RUN_CMD="$PKG_MANAGER start"
fi
;;
python)
TEST_CMD="pytest"
LINT_CMD="ruff check ."
FORMAT_CMD="ruff format ."
TYPECHECK_CMD="mypy ."
;;
rust)
RUN_CMD="cargo run"
TEST_CMD="cargo test"
LINT_CMD="cargo clippy -- -D warnings"
FORMAT_CMD="cargo fmt"
;;
go)
RUN_CMD="go run ."
TEST_CMD="go test ./..."
LINT_CMD="go vet ./..."
FORMAT_CMD="gofmt -w ."
;;
ruby)
TEST_CMD="bundle exec rspec"
LINT_CMD="bundle exec rubocop"
;;
java)
if [ "$PKG_MANAGER" = "maven" ]; then TEST_CMD="mvn test"; else TEST_CMD="./gradlew test"; fi
;;
esac
return 0
}
# === Container helpers ===
list_running_containers() {
command -v docker >/dev/null 2>&1 || return 0
docker ps --format '{{.Names}}' 2>/dev/null || true
}
# Walk up parent dirs of host_path looking for a matching mount source in
# the container; output the corresponding container path. Falls back to
# host_path if no matching mount is found.
detect_container_workdir() {
local ctr="$1" host_path="${2:-$PWD}"
command -v docker >/dev/null 2>&1 || { echo "$host_path"; return; }
local mounts
mounts="$(docker inspect "$ctr" \
--format '{{range .Mounts}}{{.Source}}={{.Destination}}{{"\n"}}{{end}}' \
2>/dev/null || true)"
[ -z "$mounts" ] && { echo "$host_path"; return; }
local current="$host_path" rel="" src dst
while [ -n "$current" ] && [ "$current" != "/" ]; do
while IFS='=' read -r src dst; do
[ -z "$src" ] && continue
if [ "$src" = "$current" ]; then
rel="${host_path#$current}"
echo "${dst}${rel}"
return 0
fi
done <<< "$mounts"
current="$(dirname "$current")"
done
echo "$host_path"
}
container_exists() {
command -v docker >/dev/null 2>&1 || return 1
docker ps -a --format '{{.Names}}' 2>/dev/null | grep -qx "$1"
}
container_running() {
command -v docker >/dev/null 2>&1 || return 1
docker ps --format '{{.Names}}' 2>/dev/null | grep -qx "$1"
}
# === Interactive setup ===
interactive_setup() {
step "Configure dev environment"
if [ "$ASSUME_YES" -eq 1 ]; then
log "running with --yes; using all defaults"
fi
if [ "$NO_DOCKER" -eq 1 ]; then
HAS_DOCKER=0
log "running in --no-docker mode; commands will run on the host"
else
pick_dev_container
fi
echo
if [ "$HAS_DOCKER" -eq 1 ]; then
echo "${C_BD}Run commands inside the container${C_OFF}"
hint "These are the exact commands you would type after 'docker exec -it $DOCKER_CONTAINER bash'"
hint "Press Enter to accept the default, or type your own. Leave blank to skip a command."
else
echo "${C_BD}Commands (host mode)${C_OFF}"
hint "Press Enter to accept the default, or type your own. Leave blank to skip a command."
fi
echo
prompt_with_default "Run the app (start dev server)" "$RUN_CMD" RUN_CMD
prompt_with_default "Run tests" "$TEST_CMD" TEST_CMD
prompt_with_default "Lint" "$LINT_CMD" LINT_CMD
prompt_with_default "Format" "$FORMAT_CMD" FORMAT_CMD
prompt_with_default "Typecheck" "$TYPECHECK_CMD" TYPECHECK_CMD
}
pick_dev_container() {
echo
echo "${C_BD}Docker dev container${C_OFF}"
if [ -n "$EXPLICIT_CONTAINER" ]; then
DOCKER_CONTAINER="$EXPLICIT_CONTAINER"
log "using --container: $DOCKER_CONTAINER"
else
local containers count=0
containers="$(list_running_containers)"
if [ -n "$containers" ]; then
count=$(printf '%s\n' "$containers" | grep -c .)
fi
if [ "$count" -eq 0 ]; then
log "no running docker containers found"
if [ "$ASSUME_YES" -eq 1 ] || [ ! -t 0 ]; then
warn "non-interactive and no containers; falling back to host mode"
HAS_DOCKER=0
return
fi
local answer=""
printf ' Enter a container name (or leave blank for host mode): '
IFS= read -r answer || answer=""
if [ -z "$answer" ]; then HAS_DOCKER=0; return; fi
DOCKER_CONTAINER="$answer"
else
echo " Running containers:"
local i=1 line
local ctr_array=()
while IFS= read -r line; do
[ -z "$line" ] && continue
ctr_array+=("$line")
printf ' %d) %s\n' "$i" "$line"
i=$((i+1))
done <<< "$containers"
local skip_idx=$((${#ctr_array[@]}+1))
printf ' %d) (none — host mode)\n' "$skip_idx"
if [ "$ASSUME_YES" -eq 1 ] || [ ! -t 0 ]; then
DOCKER_CONTAINER="${ctr_array[0]}"
log "auto-picked first container: $DOCKER_CONTAINER (use --container to override)"
else
local choice=""
printf ' Choose [1-%d, or type a name]: ' "$skip_idx"
IFS= read -r choice || choice=""
if [ -z "$choice" ] || [ "$choice" = "$skip_idx" ]; then
HAS_DOCKER=0; return
fi
if printf '%s' "$choice" | grep -qE '^[0-9]+$' \
&& [ "$choice" -ge 1 ] && [ "$choice" -le "${#ctr_array[@]}" ]; then
DOCKER_CONTAINER="${ctr_array[$((choice-1))]}"
else
DOCKER_CONTAINER="$choice"
fi
fi
fi
fi
if ! container_exists "$DOCKER_CONTAINER"; then
warn "container '$DOCKER_CONTAINER' does not exist on this machine yet"
hint "wrappers will still be generated; create or start the container before using 'make' targets"
elif ! container_running "$DOCKER_CONTAINER"; then
log "container '$DOCKER_CONTAINER' exists but is stopped; 'make up' will start it"
fi
HAS_DOCKER=1
ok "dev container: $DOCKER_CONTAINER"
local default_wd="$EXPLICIT_WORKDIR"
if [ -z "$default_wd" ]; then
default_wd="$(detect_container_workdir "$DOCKER_CONTAINER" "$PWD")"
fi
prompt_with_default "Workdir inside the container" "$default_wd" DOCKER_WORKDIR
ok "workdir: $DOCKER_WORKDIR"
}
# === Wrapper script generators ===
generate_dev_scripts_container() {
local ctr="$DOCKER_CONTAINER" wd="$DOCKER_WORKDIR"
write_executable "$SCRIPTS_DIR/dev-up.sh" "#!/usr/bin/env bash
# Ensure the dev container '$ctr' is running. The container is treated as
# pre-existing (created outside this repo); we start it if stopped.
set -euo pipefail
if docker ps --format '{{.Names}}' | grep -qx '$ctr'; then
echo \"container '$ctr' already running\"
exit 0
fi
if docker ps -a --format '{{.Names}}' | grep -qx '$ctr'; then
docker start '$ctr'
else
echo \"ERROR: container '$ctr' does not exist on this machine.\" >&2
echo \"Create it manually first; the dev container is managed outside this repo.\" >&2
exit 1
fi
"
write_executable "$SCRIPTS_DIR/dev-down.sh" "#!/usr/bin/env bash
# This is a SHARED dev container. We do NOT stop it automatically.
echo \"Container '$ctr' is shared. Not stopping it.\"
echo \"To stop it manually: docker stop '$ctr'\"
"
write_executable "$SCRIPTS_DIR/dev-shell.sh" "#!/usr/bin/env bash
set -euo pipefail
exec docker exec -it -w '$wd' '$ctr' sh -lc \"\${SHELL:-bash} || sh\"
"
write_executable "$SCRIPTS_DIR/dev-run.sh" "#!/usr/bin/env bash
# Run an arbitrary command inside the dev container '$ctr' at workdir '$wd'.
# Usage: ./scripts/dev-run.sh <command...>
# Auto-starts the container if it is not running.
set -euo pipefail
cd \"\$(dirname \"\$0\")/..\"
if ! docker ps --format '{{.Names}}' | grep -qx '$ctr'; then
./scripts/dev-up.sh
fi
exec docker exec -w '$wd' '$ctr' \"\$@\"
"
write_executable "$SCRIPTS_DIR/dev-logs.sh" "#!/usr/bin/env bash
set -euo pipefail
exec docker logs -f --tail=200 '$ctr'
"
emit_command_wrapper "dev-start.sh" "$RUN_CMD"
emit_command_wrapper "dev-test.sh" "$TEST_CMD"
emit_command_wrapper "dev-lint.sh" "$LINT_CMD"
emit_command_wrapper "dev-format.sh" "$FORMAT_CMD"
emit_command_wrapper "dev-typecheck.sh" "$TYPECHECK_CMD"
}
generate_dev_scripts_host() {
emit_host_wrapper "dev-start.sh" "$RUN_CMD"
emit_host_wrapper "dev-test.sh" "$TEST_CMD"
emit_host_wrapper "dev-lint.sh" "$LINT_CMD"
emit_host_wrapper "dev-format.sh" "$FORMAT_CMD"
emit_host_wrapper "dev-typecheck.sh" "$TYPECHECK_CMD"
}
emit_command_wrapper() {
local name="$1" cmd="$2"
[ -z "$cmd" ] && return 0
write_executable "$SCRIPTS_DIR/$name" "#!/usr/bin/env bash
set -euo pipefail
cd \"\$(dirname \"\$0\")/..\"
exec ./$SCRIPTS_DIR/dev-run.sh $cmd \"\$@\"
"
}
emit_host_wrapper() {
local name="$1" cmd="$2"
[ -z "$cmd" ] && return 0
write_executable "$SCRIPTS_DIR/$name" "#!/usr/bin/env bash
set -euo pipefail
cd \"\$(dirname \"\$0\")/..\"
exec $cmd \"\$@\"
"
}
# === Makefile ===
generate_makefile() {
local docker_targets=""
if [ "$HAS_DOCKER" -eq 1 ]; then
docker_targets='
up: ## Start the dev container
@./scripts/dev-up.sh
down: ## Notice — does not actually stop the shared container
@./scripts/dev-down.sh
shell: ## Open an interactive shell inside the dev container
@./scripts/dev-shell.sh
run: ## Run an arbitrary command inside the container — usage: make run CMD="..."
@./scripts/dev-run.sh $(CMD)
logs: ## Tail dev container logs
@./scripts/dev-logs.sh
'
fi
local cmd_targets=""
[ -n "$RUN_CMD" ] && cmd_targets="$cmd_targets
start: ## Start the app (run dev server)
@./scripts/dev-start.sh
"
[ -n "$TEST_CMD" ] && cmd_targets="$cmd_targets
test: ## Run tests (extra args: make test ARGS=\"-k pattern\")
@./scripts/dev-test.sh \$(ARGS)
"
[ -n "$LINT_CMD" ] && cmd_targets="$cmd_targets
lint: ## Run linter
@./scripts/dev-lint.sh
"
[ -n "$FORMAT_CMD" ] && cmd_targets="$cmd_targets
format: ## Format code
@./scripts/dev-format.sh
"
[ -n "$TYPECHECK_CMD" ] && cmd_targets="$cmd_targets
typecheck: ## Run type checker
@./scripts/dev-typecheck.sh
"
local check_deps=""
[ -n "$LINT_CMD" ] && check_deps="$check_deps lint"
[ -n "$TYPECHECK_CMD" ] && check_deps="$check_deps typecheck"
[ -n "$TEST_CMD" ] && check_deps="$check_deps test"
check_deps="${check_deps# }"
local check_target=""
[ -n "$check_deps" ] && check_target="
check: $check_deps ## Run all quality gates (lint + typecheck + test)
"
local content="# Makefile — runnable shortcuts. Type 'make' or 'make help'.
# Targets call scripts/dev-*.sh, so the docker/host distinction lives
# in ONE place (the scripts) — not here.
.DEFAULT_GOAL := help
.PHONY: help up down shell run start test lint format typecheck check logs clean
help: ## Show this help
@awk 'BEGIN {FS = \":.*?## \"} /^[a-zA-Z_-]+:.*?## / {printf \" \\033[36m%-12s\\033[0m %s\\n\", \$\$1, \$\$2}' \$(MAKEFILE_LIST)
${docker_targets}${cmd_targets}${check_target}
clean: ## Remove caches and build artefacts
@rm -rf .pytest_cache .ruff_cache .mypy_cache __pycache__ \\
node_modules/.cache .next/cache target/debug/incremental 2>/dev/null || true
@find . -name '*.pyc' -delete 2>/dev/null || true
@echo \"cleaned local caches\"
"
write_file "Makefile" "$content"
}
# === .claude/PLUGINS.md ===
generate_plugins_doc() {
local content="# Required Claude Code plugins
These plugins extend Claude Code with skills tailored to this repo's workflow.
Plugins install at the **user level** (\`~/.claude/plugins/\`), so you only run
these commands once per machine — they survive across sessions and across repos.
## First-time setup
Open Claude Code in this repo, then run each slash command in turn:
\`\`\`
$PLUGINS_BLOCK
\`\`\`
After install, type \`/plugins\` to verify the plugins are listed.
## What you get
- **andrej-karpathy-skills** — curated skills derived from Andrej Karpathy's
engineering and ML practices (tight feedback loops, minimal repros, clean
experiment hygiene). Source:
https://github.com/forrestchang/andrej-karpathy-skills
## Updating / removing
- Update: \`/plugin update --all\`
- Remove: \`/plugin uninstall andrej-karpathy-skills\`
"
write_file "$CLAUDE_DIR/PLUGINS.md" "$content"
}
# === .claude/settings.json ===
generate_settings() {
local docker_perms=""
if [ "$HAS_DOCKER" -eq 1 ]; then
docker_perms='
"Bash(./scripts/dev-up.sh:*)",
"Bash(./scripts/dev-down.sh:*)",
"Bash(./scripts/dev-run.sh:*)",
"Bash(./scripts/dev-shell.sh:*)",
"Bash(./scripts/dev-logs.sh:*)",'
fi
write_file "$CLAUDE_DIR/settings.json" "{
\"permissions\": {
\"allow\": [
\"Bash(git status)\",
\"Bash(git diff:*)\",
\"Bash(git log:*)\",
\"Bash(git add:*)\",
\"Bash(git commit:*)\",
\"Bash(git checkout:*)\",
\"Bash(git branch:*)\",
\"Bash(git stash:*)\",
\"Bash(ls:*)\",
\"Bash(cat:*)\",
\"Bash(grep:*)\",
\"Bash(rg:*)\",
\"Bash(find:*)\",
\"Bash(make:*)\",
\"Bash(./scripts/dev-start.sh:*)\",
\"Bash(./scripts/dev-test.sh:*)\",
\"Bash(./scripts/dev-lint.sh:*)\",
\"Bash(./scripts/dev-format.sh:*)\",
\"Bash(./scripts/dev-typecheck.sh:*)\",${docker_perms}
\"Read\", \"Edit\", \"Write\", \"Glob\", \"Grep\"
],
\"deny\": [
\"Bash(sudo:*)\",
\"Bash(rm -rf /*)\",
\"Bash(curl:* | sh)\",
\"Edit(.env)\",
\"Edit(.env.*)\",
\"Edit(secrets/*)\"
]
}
}"
}
generate_agents() {
write_file "$CLAUDE_DIR/agents/researcher.md" '---
name: researcher
description: Explores the codebase to answer questions or summarize areas without modifying files. Use when the main session needs context about a feature, module, or pattern.
tools: Read, Grep, Glob, Bash
model: haiku
---
You are a research assistant. Read-only.
Rules:
- Never edit, write, or run mutating commands.
- Be specific: cite file paths and line numbers.
- Be concise: summarize, do not paste large code blocks unless asked.
- If asked to "find pattern X", report 3-5 best examples, not all matches.
- End every report with: "Files most worth reading next: ..." (max 3).
'
write_file "$CLAUDE_DIR/agents/code-reviewer.md" '---
name: code-reviewer
description: Reviews diffs or specific files for correctness, edge cases, and consistency with project conventions. Use after implementing a change, before commit.
tools: Read, Grep, Glob, Bash
model: opus
---
Senior engineer doing a careful code review. Read-only.
Look for:
- Logic errors and unhandled edge cases.
- Security issues (injection, auth/authz gaps, secrets).
- Inconsistency with existing project patterns.
- Missing or weak tests for the change.
For each issue: file and line, 1-sentence problem, concrete fix.
End with: APPROVE / REQUEST_CHANGES / BLOCK.
'
write_file "$CLAUDE_DIR/agents/test-writer.md" '---
name: test-writer
description: Writes tests for a given module or function, following the project test conventions. Use when adding tests to an implementation.
tools: Read, Edit, Write, Grep, Glob, Bash
model: sonnet
---
You write tests that match the project'"'"'s existing test style.
Process:
1. Read 2-3 existing test files to learn the pattern.
2. Read the code being tested.
3. Write tests covering: happy path, key edge cases, error paths.
4. Run via `make test` and fix failures.
5. Stop when tests pass. Do not refactor production code unless asked.
'
}
generate_skills() {
write_file "$CLAUDE_DIR/skills/commit-and-pr/SKILL.md" '---
name: commit-and-pr
description: Use when the user asks to commit, push, or open a pull request. Ensures consistent commit messages and PR descriptions.
---
# Commit and PR workflow
1. Run `git status` and `git diff --staged` to see what is being committed.
2. If nothing is staged, ask which files to include rather than guessing.
3. Run `make check` (or individual `make lint typecheck test`) before committing. Do not commit if they fail.
4. Commit message format:
```
<type>: <imperative summary, <72 chars>
<optional body wrapped at 72 chars>
```
Types: feat, fix, refactor, docs, test, chore, perf.
5. Do NOT add Co-Authored-By unless requested.
6. For PRs: `gh pr create` with body covering what changed, why, how it was tested, what reviewers should focus on.
'
write_file "$CLAUDE_DIR/skills/plan-before-implement/SKILL.md" '---
name: plan-before-implement
description: Use for any change that touches more than one file or that the user describes as a feature, refactor, or migration. Forces a written plan before any code is written.
---
# Plan before implementing
For any non-trivial change:
1. Enter plan mode (read-only). Identify files to change, files to create, external dependencies, and edge cases.
2. Use AskUserQuestion to surface decisions you are uncertain about. Do not guess silently.
3. Write the plan to `SPEC.md` with sections: Goal, Approach, Files to change, Test strategy, Open questions.
4. Stop and ask the user to review SPEC.md before implementing.
5. After approval, start a fresh session and reference `@SPEC.md` to implement.
Skip only if the change is a one-line fix or the user explicitly says "just do it".
'
write_file "$CLAUDE_DIR/skills/diagrams/SKILL.md" '---
name: diagrams
description: Use when implementing a new feature with multi-step or multi-service flow, when the architecture changes (new service, removed component, new external dependency), when investigating a bug across multiple components, or when onboarding to a new flow. Defines when to draw component vs sequence diagrams and how to keep them in sync with code.
---
# Architecture diagrams
This project uses **Mermaid** diagrams in `docs/architecture/`. Two types only:
- `docs/architecture/components.md` — single high-level component view (one file, kept current)
- `docs/architecture/sequences/<feature>.md` — one file per multi-step flow
Read existing diagrams with `@docs/architecture/` before drawing new ones.
## When to draw / update
| Trigger | What to do |
|----------------------------------------------------------------------|---------------------------------------------------------|
| New feature with async or multi-service flow | Create new `sequences/<feature-slug>.md` BEFORE coding |
| New service, removed component, new external dependency | Update `components.md` in the same PR as the code change |
| Bug investigation crossing multiple components | Optionally draft a sequence to map current behavior |
| Onboarding to a flow you do not understand | Read existing diagram first; ask if missing |
## When to skip
- One-file synchronous change → no diagram
- Pure refactor preserving behavior → no diagram (existing diagrams still valid)
- Throwaway script / experiment → no diagram
## Workflow
### Plan phase (in plan mode)
1. Read existing diagrams: `@docs/architecture/` and any related sequences.
2. Draft the new sequence diagram. Save to `docs/architecture/sequences/<slug>.md`.
3. STOP. Print the Mermaid source for the user to review. Do not implement.
### Implement phase (new session)
1. Reference: `@docs/architecture/sequences/<slug>.md @SPEC.md`.
2. Match code structure to the diagram — participant names = service/class names, message names = method names.
3. Run `make check` after.
### Review phase (after implementation)
1. Compare implementation to diagram.
2. List deviations (if any) with file:line references.
3. Ask the user: update diagram to match code, OR fix code to match diagram?
4. Do NOT auto-update either side without confirmation.
### Architecture change
If the change affects which services exist, who calls whom, or external dependencies:
1. Update `components.md` in the same commit.
2. Add a row to its "Update log" table with date + PR link.
## Mermaid conventions
**Sequence**:
- Always `autonumber` so the user can reference steps as "step 3".
- Use `participant` declarations explicitly; name them after actual classes/services.
- Use `Note over X: ...` for state changes; do not skip them.
- Distinguish sync (`->>`) from async (`-->>`); show return arrows.
**Component (graph TB or LR)**:
- Group with `subgraph` (e.g. `external`, `system`, `data`).
- Shapes: `[Box]` services, `[(Cylinder)]` databases, `[[Subprocess]]` external/3rd-party.
- < 10 nodes per diagram; if more, split into sub-views and link them in `README.md`.
- Keep edge labels short (max 3 words). Detail goes in the table below the diagram.
## Anti-patterns
- Drawing diagrams Claude has not verified against actual code (hallucinated components).
- Class diagrams listing every method/property — they go stale in days.
- Updating diagrams without updating the "Update log" — drift becomes invisible.
- Diagrams that need a separate render server (PlantUML, draw.io). Mermaid only.
'
if [ "$HAS_DOCKER" -eq 1 ]; then
write_file "$CLAUDE_DIR/skills/run-in-docker/SKILL.md" "---
name: run-in-docker
description: Use whenever a shell command (test, lint, package install, framework CLI, database query, migration, running the app) needs to run for this project. The dev container is '$DOCKER_CONTAINER'; commands on the host will fail or use the wrong environment.
---
# Run commands inside the dev container
The dev container is **\`$DOCKER_CONTAINER\`**, with the repo mounted at **\`$DOCKER_WORKDIR\`** inside it. Never run language-level commands (pytest, npm, ruff, mypy, manage.py, rails, etc.) directly on the host.
## How to run things
\`make\` is the canonical entry point. All targets call \`scripts/dev-*.sh\`, which wrap commands with \`docker exec -w $DOCKER_WORKDIR $DOCKER_CONTAINER\`.
| Need | Command |
|-------------------------------|--------------------------------------|
| Start the dev container | \`make up\` |
| Open a shell in the container | \`make shell\` |
| Run the app / dev server | \`make start\` |
| Run tests | \`make test\` |
| Run all quality gates | \`make check\` |
| Lint / format / typecheck | \`make lint\` / \`format\` / \`typecheck\` |
| Arbitrary command | \`make run CMD=\"...\"\` |
| Tail logs | \`make logs\` |
Wrappers auto-start the container, so a single \`make test\` is enough.
## Rules
- ALWAYS prefer \`make\` over raw \`docker exec\`. If a target doesn't exist, use \`make run CMD=\"...\"\`. If the command will be reused, add a target to the Makefile and a wrapper to \`scripts/\`.
- The container is **shared/pre-existing** (created outside this repo). Do NOT \`docker rm\`, \`docker stop\`, or \`docker compose down\` it. \`make down\` is intentionally a no-op.
- Host-only commands (git, gh, file edits, make itself) run normally on the host.
- If a wrapper fails with \"container does not exist\", the container is missing on this machine. Don't try to create it from here — that's an out-of-band operation.
"
fi
}
generate_hooks_example() {
write_file "$CLAUDE_DIR/hooks-example.json" '{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": "make format >/dev/null 2>&1 || true" }
]
}
]
}
}'
}
update_gitignore() {
if [ "$DRY_RUN" -eq 1 ]; then log "[dry-run] would update .gitignore"; return; fi
[ ! -f .gitignore ] && touch .gitignore
local entries=(".claude/CLAUDE.local.md" ".claude/settings.local.json")
local added=0
for e in "${entries[@]}"; do
if ! grep -qxF "$e" .gitignore 2>/dev/null; then
echo "$e" >> .gitignore
added=$((added+1))
fi
done
if [ "$added" -gt 0 ]; then ok "updated .gitignore (+$added entries)"; fi
return 0
}
# === Architecture diagram templates ===
# Creates docs/architecture/ with Mermaid skeletons for component + sequence diagrams.
# These are intentionally placeholders — Claude fills them in during the first
# planning session (the 'diagrams' skill instructs it).
generate_diagram_templates() {
write_file "docs/architecture/README.md" "# Architecture documentation
Mermaid diagrams describing this system. Two types only:
- \`components.md\` — high-level component view (one file, kept current)
- \`sequences/<feature>.md\` — one file per feature/flow
## Rules
- Mermaid syntax (renders in GitHub, GitLab, IDE plugins)
- Updated in the **same PR** as architectural code changes
- Kept at \"30-second understanding\" level — not exhaustive
- Sequences live under \`sequences/\`, component diagram is single \`components.md\`
## Conventions
| Diagram | When to update |
|-------------|---------------------------------------------------------|
| components | New service, removed component, new external dependency |
| sequence | New multi-step / async / multi-service feature |
If a feature is one-file synchronous logic, no sequence is needed.
"
write_file "docs/architecture/components.md" "# Component diagram
> Replace this placeholder with the real components of $PROJECT_NAME.
> Keep it under ~10 nodes; split into sub-views if larger.
\`\`\`mermaid
graph TB
subgraph external [\"External\"]
User[User / Client]
end
subgraph system [\"$PROJECT_NAME\"]
API[API Service]
Worker[Background Worker]
DB[(Database)]
end
User --> API
API --> DB
API -->|enqueue| Worker
Worker --> DB
classDef ext fill:#f5f5f5,stroke:#999,stroke-dasharray:5 5
class User ext
\`\`\`
## Component responsibilities
| Component | Responsibility |
|-----------|----------------|
| API | (TODO) |
| Worker | (TODO) |
| Database | (TODO) |
## Update log
| Date | Change | PR |
|------|--------|-----|
"
write_file "docs/architecture/sequences/_template.md" "# <Feature name> sequence
> Copy this file to \`<feature-slug>.md\` and fill in. Delete this header.
## Flow
\`\`\`mermaid
sequenceDiagram
autonumber
participant Client
participant API
participant Service
participant DB
Client->>API: POST /endpoint
API->>Service: process(payload)
Service->>DB: write(record)
DB-->>Service: ack
Service-->>API: result
API-->>Client: 200 OK
\`\`\`
## Edge cases handled
- (TODO) what happens if Service is down?
- (TODO) what happens on DB write failure?
- (TODO) what happens on duplicate request?
## Related
- Spec: <link to SPEC.md or issue>
- Code: <link to main entry point>
"
}
# === CLAUDE.md ===
write_template_claude_md() {
local docker_section=""
if [ "$HAS_DOCKER" -eq 1 ]; then
docker_section="
## Docker workflow
Dev container: **\`$DOCKER_CONTAINER\`** (repo mounted at \`$DOCKER_WORKDIR\` inside it). The container is **pre-existing/shared** — created outside this repo and not stopped automatically.
**Never run language-level commands on the host** — use \`make\` targets, which wrap commands with \`docker exec -w $DOCKER_WORKDIR $DOCKER_CONTAINER\`. For ad-hoc commands without a target, use \`make run CMD=\"...\"\`.
"
fi
local cmd_lines=""
[ -n "$RUN_CMD" ] && cmd_lines="$cmd_lines
- \`make start\` — run the app / dev server"
[ -n "$TEST_CMD" ] && cmd_lines="$cmd_lines
- \`make test\` — run tests"
[ -n "$LINT_CMD" ] && cmd_lines="$cmd_lines
- \`make lint\` — lint"
[ -n "$FORMAT_CMD" ] && cmd_lines="$cmd_lines
- \`make format\` — format"
[ -n "$TYPECHECK_CMD" ] && cmd_lines="$cmd_lines
- \`make typecheck\` — type-check"
cmd_lines="$cmd_lines
- \`make check\` — full quality gate (lint + typecheck + test)"
if [ "$HAS_DOCKER" -eq 1 ]; then
cmd_lines="$cmd_lines
- \`make shell\` — interactive shell inside the dev container
- \`make run CMD=\\\"<cmd>\\\"\` — run any command inside the dev container"
fi
write_file "CLAUDE.md" "# $PROJECT_NAME
> Read at the start of every Claude Code session. Keep short and pruned.
## First-time setup
Plugins required: see \`.claude/PLUGINS.md\` and run those slash commands once per machine.
## Stack
- Language: $LANGUAGE
- Package manager: $PKG_MANAGER
$( [ "$HAS_DOCKER" -eq 1 ] && printf -- '- Dev container: `%s` (workdir `%s`)' "$DOCKER_CONTAINER" "$DOCKER_WORKDIR" )
## Commands
All runnable commands live in the **Makefile** — type \`make help\` for the full list.
$cmd_lines
$docker_section
## Workflow
- Use plan mode for any change touching >1 file. Write the plan to SPEC.md before implementing.
- For multi-step or multi-service features, draft a Mermaid sequence in \`docs/architecture/sequences/<slug>.md\` before coding (see the diagrams skill).
- Run \`make check\` before committing. Do not commit on red.
- Commits: \`<type>: <imperative summary>\` (feat|fix|refactor|docs|test|chore|perf). No Co-Authored-By unless requested.
- For exploration that would read many files, dispatch the researcher subagent.
- Run \`/clear\` between unrelated tasks.
## Architecture diagrams
Two diagram types in \`docs/architecture/\` (Mermaid only):
- \`components.md\` — single component overview, updated when architecture changes
- \`sequences/<feature>.md\` — one per async / multi-service feature
Update diagrams in the **same PR** as architectural code changes. Read existing diagrams (\`@docs/architecture/\`) before drafting new ones.
## Code style
<!-- TODO: replace with project-specific rules. -->
## Gotchas
<!-- TODO: list things Claude would get wrong without being told. -->
## Repository etiquette
- Branch naming: \`<type>/<short-slug>\`.
- One PR per logical change; keep diffs <500 lines when possible.
- Squash-merge into main.
"
}
invoke_claude_for_claude_md() {
if [ "$SKIP_CLAUDE_MD" -eq 1 ]; then
log "skipping claude invocation (--no-claude-md)"; write_template_claude_md; return
fi
if ! command -v claude >/dev/null 2>&1; then
warn "claude CLI not found — falling back to template CLAUDE.md"; write_template_claude_md; return
fi
if [ "$DRY_RUN" -eq 1 ]; then log "[dry-run] would invoke claude -p"; return; fi
if [ -e CLAUDE.md ] && [ "$FORCE" -ne 1 ]; then warn "skip CLAUDE.md (exists)"; return; fi
step "Asking Claude to draft CLAUDE.md from the codebase"
local docker_hint=""
if [ "$HAS_DOCKER" -eq 1 ]; then
docker_hint="
The dev container is '$DOCKER_CONTAINER' with repo at '$DOCKER_WORKDIR'.
Wrappers call 'docker exec -w $DOCKER_WORKDIR $DOCKER_CONTAINER <cmd>'.
The CLAUDE.md MUST instruct Claude to use 'make' targets and never run
language commands on the host."
fi
local prompt="Read this repository and draft a high-quality CLAUDE.md (Claude Code memory file).
Detected facts:
- Project: $PROJECT_NAME
- Language: $LANGUAGE / $PKG_MANAGER
- Run app: ${RUN_CMD:-(none set)}
- Test: ${TEST_CMD:-(none)}
- Lint: ${LINT_CMD:-(none)}
- Format: ${FORMAT_CMD:-(none)}
- Typecheck: ${TYPECHECK_CMD:-(none)}
- Has docker: $HAS_DOCKER$docker_hint
Required plugins are in .claude/PLUGINS.md (already generated). Mention this in a 'First-time setup' section near the top.
Requirements:
1. Under 150 lines. Every line must answer: would Claude make a mistake without this?
2. Sections in order: First-time setup, Stack, Commands (point to make targets), Workflow, Code style, Gotchas, Repository etiquette. Add Docker workflow section if Docker is in use.
3. Code style: read existing source files and infer ACTUAL conventions in use. No generic advice.
4. Gotchas: read README, scripts, configs and surface non-obvious things.
5. Workflow: include plan-before-implement, /clear between tasks, subagent for big exploration. Mention 'make check' before commit.
6. Markdown only. No emojis. Code spans for commands.
Write CLAUDE.md to repo root. Output nothing else."
if claude -p "$prompt" --permission-mode acceptEdits >/dev/null 2>&1 && [ -f CLAUDE.md ]; then
ok "CLAUDE.md drafted by Claude Code"
else
warn "claude invocation failed or no file produced — using template"
write_template_claude_md
fi
}
# === CLI ===
print_help() {
cat <<EOF
claude-init.sh v$SCRIPT_VERSION
Bootstrap Claude Code workflow for the current repository. Asks you for the
docker container that's the dev environment and the actual commands you run
inside it; auto-detected values are offered as defaults.
Usage: $(basename "$0") [options]
Options:
--container <name> Use this docker container as dev environment (skip prompt)
--workdir <path> Workdir inside the container (skip prompt)
--no-docker Run all commands on the host; no docker
--yes Accept all defaults non-interactively
--no-claude-md Skip 'claude -p' draft of CLAUDE.md (use template)
--force Overwrite existing files
--dry-run Don't write anything
--help Show this help
What it does:
1. Auto-detects language and package manager → proposes default commands.
2. Asks (interactively, with defaults):
- Which running container to use as the dev environment
- The workdir inside the container (where the repo is mounted)
- The exact commands you run for: app, tests, lint, format, typecheck
3. Generates ./scripts/dev-*.sh wrappers using docker exec.
4. Generates a Makefile (single source of truth for runnable commands).
5. Generates .claude/ config: settings, agents, skills, plugin list, hooks example.
6. Drafts CLAUDE.md via 'claude -p' (or falls back to a template).
Re-run safely: existing files are skipped unless --force.
EOF
}
parse_args() {
while [ $# -gt 0 ]; do
case "$1" in
--container) EXPLICIT_CONTAINER="${2:-}"; shift 2 ;;
--container=*) EXPLICIT_CONTAINER="${1#*=}"; shift ;;
--workdir) EXPLICIT_WORKDIR="${2:-}"; shift 2 ;;
--workdir=*) EXPLICIT_WORKDIR="${1#*=}"; shift ;;
--no-docker) NO_DOCKER=1; shift ;;
--yes|-y) ASSUME_YES=1; shift ;;
--no-claude-md) SKIP_CLAUDE_MD=1; shift ;;
--force) FORCE=1; shift ;;
--dry-run) DRY_RUN=1; shift ;;
--help|-h) print_help; exit 0 ;;
*) err "unknown option: $1"; print_help; exit 2 ;;
esac
done
}
print_pipeline_overview() {
cat <<EOF
${C_BD}╭──────────────────────────────────────────────────────────────╮${C_OFF}
${C_BD}│ claude-init.sh v$SCRIPT_VERSION — pipeline overview │${C_OFF}
${C_BD}╰──────────────────────────────────────────────────────────────╯${C_OFF}
${C_BD}Phase 1: Configure (interactive)${C_OFF}
${C_DM}◯${C_OFF} 1. Detect project → language, package manager
${C_DM}◯${C_OFF} 2. Pick dev container → from running containers
${C_DM}◯${C_OFF} 3. Configure run commands → run, test, lint, format, typecheck
${C_BD}Phase 2: Generate${C_OFF}
${C_DM}◯${C_OFF} 4. Wrapper scripts → scripts/dev-*.sh
${C_DM}◯${C_OFF} 5. Makefile → Makefile
${C_DM}◯${C_OFF} 6. Claude Code config → .claude/
${C_DM}◯${C_OFF} 7. Architecture diagrams → docs/architecture/
${C_DM}◯${C_OFF} 8. CLAUDE.md → CLAUDE.md
EOF
}
print_summary() {
step "Configuration"
printf ' %-16s %s\n' "Project:" "$PROJECT_NAME"
printf ' %-16s %s\n' "Language:" "$LANGUAGE / $PKG_MANAGER"
if [ "$HAS_DOCKER" -eq 1 ]; then
printf ' %-16s %s\n' "Dev container:" "$DOCKER_CONTAINER"
printf ' %-16s %s\n' "Workdir:" "$DOCKER_WORKDIR"
else
printf ' %-16s %s\n' "Dev container:" "(none — host mode)"
fi
echo " Commands:"
[ -n "$RUN_CMD" ] && printf ' %-12s %s\n' "start" "$RUN_CMD"
[ -n "$TEST_CMD" ] && printf ' %-12s %s\n' "test" "$TEST_CMD"
[ -n "$LINT_CMD" ] && printf ' %-12s %s\n' "lint" "$LINT_CMD"
[ -n "$FORMAT_CMD" ] && printf ' %-12s %s\n' "format" "$FORMAT_CMD"
[ -n "$TYPECHECK_CMD" ] && printf ' %-12s %s\n' "typecheck" "$TYPECHECK_CMD"
return 0
}
print_completion_report() {
cat <<EOF
${C_G}${C_BD}╭──────────────────────────────────────────────────────────────╮${C_OFF}
${C_G}${C_BD}│ ✓ Setup complete │${C_OFF}
${C_G}${C_BD}╰──────────────────────────────────────────────────────────────╯${C_OFF}
${C_BD}Pipeline${C_OFF}
${C_G}✓${C_OFF} 1. Detect project → $LANGUAGE / $PKG_MANAGER
EOF
if [ "$HAS_DOCKER" -eq 1 ]; then
printf ' %s✓%s 2. Pick dev container → %s\n' "$C_G" "$C_OFF" "$DOCKER_CONTAINER"
else
printf ' %s✓%s 2. Pick dev container → (host mode)\n' "$C_G" "$C_OFF"
fi
local cmd_count=0
[ -n "$RUN_CMD" ] && cmd_count=$((cmd_count+1))
[ -n "$TEST_CMD" ] && cmd_count=$((cmd_count+1))
[ -n "$LINT_CMD" ] && cmd_count=$((cmd_count+1))
[ -n "$FORMAT_CMD" ] && cmd_count=$((cmd_count+1))
[ -n "$TYPECHECK_CMD" ] && cmd_count=$((cmd_count+1))
printf ' %s✓%s 3. Configure run commands → %d command(s) set\n' "$C_G" "$C_OFF" "$cmd_count"
local script_count=0
if [ "$HAS_DOCKER" -eq 1 ]; then script_count=$((script_count+5)); fi
script_count=$((script_count + cmd_count))
printf ' %s✓%s 4. Wrapper scripts → scripts/ (%d file%s)\n' "$C_G" "$C_OFF" "$script_count" "$( [ "$script_count" -ne 1 ] && echo s )"
printf ' %s✓%s 5. Makefile → Makefile\n' "$C_G" "$C_OFF"
local claude_files=5 # settings, PLUGINS, hooks-example, 3 agents = 6, 2-3 skills (now incl diagrams)
if [ "$HAS_DOCKER" -eq 1 ]; then claude_files=10; else claude_files=9; fi
printf ' %s✓%s 6. Claude Code config → .claude/ (%d files)\n' "$C_G" "$C_OFF" "$claude_files"
printf ' %s✓%s 7. Architecture diagrams → docs/architecture/ (3 files)\n' "$C_G" "$C_OFF"
printf ' %s✓%s 8. CLAUDE.md → CLAUDE.md\n' "$C_G" "$C_OFF"
cat <<EOF
${C_BD}Configuration${C_OFF}
EOF
printf ' Project %s\n' "$PROJECT_NAME"
printf ' Language %s / %s\n' "$LANGUAGE" "$PKG_MANAGER"
if [ "$HAS_DOCKER" -eq 1 ]; then
printf ' Dev container %s\n' "$DOCKER_CONTAINER"
printf ' Workdir %s\n' "$DOCKER_WORKDIR"
else
echo " Mode host (no docker)"
fi
echo
if [ "$HAS_DOCKER" -eq 1 ]; then
echo " ${C_BD}Commands${C_OFF} (run inside $DOCKER_CONTAINER)"
else
echo " ${C_BD}Commands${C_OFF}"
fi
[ -n "$RUN_CMD" ] && printf ' %-12s %s\n' "start" "$RUN_CMD"
[ -n "$TEST_CMD" ] && printf ' %-12s %s\n' "test" "$TEST_CMD"
[ -n "$LINT_CMD" ] && printf ' %-12s %s\n' "lint" "$LINT_CMD"
[ -n "$FORMAT_CMD" ] && printf ' %-12s %s\n' "format" "$FORMAT_CMD"
[ -n "$TYPECHECK_CMD" ] && printf ' %-12s %s\n' "typecheck" "$TYPECHECK_CMD"
echo
echo " ${C_BD}Files generated${C_OFF}"
echo " Makefile"
echo " CLAUDE.md"
echo " .gitignore (updated)"
echo " docs/architecture/"
echo " ├─ README.md"
echo " ├─ components.md"
echo " └─ sequences/_template.md"
echo " .claude/"
echo " ├─ settings.json"
echo " ├─ PLUGINS.md"
echo " ├─ hooks-example.json"
echo " ├─ agents/ (researcher, code-reviewer, test-writer)"
if [ "$HAS_DOCKER" -eq 1 ]; then
echo " └─ skills/ (commit-and-pr, plan-before-implement, diagrams, run-in-docker)"
else
echo " └─ skills/ (commit-and-pr, plan-before-implement, diagrams)"
fi
echo " scripts/"
if [ "$HAS_DOCKER" -eq 1 ]; then
echo " ├─ dev-up.sh, dev-down.sh, dev-shell.sh, dev-run.sh, dev-logs.sh"
fi
local cmd_scripts=()
[ -n "$RUN_CMD" ] && cmd_scripts+=("dev-start.sh")
[ -n "$TEST_CMD" ] && cmd_scripts+=("dev-test.sh")
[ -n "$LINT_CMD" ] && cmd_scripts+=("dev-lint.sh")
[ -n "$FORMAT_CMD" ] && cmd_scripts+=("dev-format.sh")
[ -n "$TYPECHECK_CMD" ] && cmd_scripts+=("dev-typecheck.sh")
if [ ${#cmd_scripts[@]} -gt 0 ]; then
local joined
joined="$(IFS=','; echo "${cmd_scripts[*]}" | sed 's/,/, /g')"
echo " └─ $joined"
fi
echo
echo " ${C_BD}Next steps${C_OFF}"
echo " 1. Open Claude Code in this repo and run (one-time per machine):"
echo " /plugin marketplace add forrestchang/andrej-karpathy-skills"
echo " /plugin install andrej-karpathy-skills@karpathy-skills"
echo " (slash commands cannot be invoked from bash; you must run them inside Claude Code)"
echo " 2. Edit CLAUDE.md — fill in the TODO sections (code style, gotchas)"
echo " 3. Try: ${C_BD}make help${C_OFF}"
if [ "$HAS_DOCKER" -eq 1 ]; then
echo " ${C_BD}make up${C_OFF} → start '$DOCKER_CONTAINER' if not running"
echo " ${C_BD}make test${C_OFF} → run tests inside the container"
fi
echo " 4. Commit .claude/, scripts/, Makefile, CLAUDE.md so the team shares the setup"
echo
return 0
}
print_next_steps() { print_completion_report; }
main() {
parse_args "$@"
print_pipeline_overview
step "claude-init.sh v$SCRIPT_VERSION"
log "running in: $(pwd)"
[ "$DRY_RUN" -eq 1 ] && warn "dry-run: no files will be written"
[ "$FORCE" -eq 1 ] && warn "force: existing files WILL be overwritten"
step "Detecting project"
detect_project_name
detect_language
default_commands
log "language: $LANGUAGE, package manager: $PKG_MANAGER"
interactive_setup
print_summary
step "Generating wrapper scripts"
if [ "$HAS_DOCKER" -eq 1 ]; then generate_dev_scripts_container
else generate_dev_scripts_host; fi
step "Generating Makefile"
generate_makefile
step "Generating .claude/ config"
generate_settings
generate_agents
generate_skills
generate_hooks_example
generate_plugins_doc
update_gitignore
step "Generating architecture diagram templates"
generate_diagram_templates
step "Generating CLAUDE.md"
invoke_claude_for_claude_md
print_next_steps
}
main "$@"
Tôi muốn build [mô tả ngắn]. Interview tôi chi tiết bằng AskUserQuestion.
Đào sâu phần khó tôi có thể chưa nghĩ tới, đừng hỏi câu hiển nhiên.
Khi đủ thông tin, viết spec đầy đủ vào SPEC.md ở repo root với sections:
Goal, Approach, Files to change, Test strategy, Open questions.
KHÔNG implement code.
Đọc @SPEC.md, @docs/architecture/sequences/<feature-slug>.md,
và @docs/architecture/components.md.
Workflow:
1. Summarize spec + sequence trong 5 dòng để confirm hiểu đúng.
2. Liệt kê file sẽ tạo/sửa (khớp với "Files to change" trong SPEC).
Đợi tôi OK.
3. Implement phase-by-phase. Tên method/class match participant
trong sequence diagram. Test trước, code sau.
4. Sau mỗi phase: chạy `make check`. Báo cáo + diff summary.
Đợi tôi confirm trước khi sang phase tiếp.
KHÔNG sửa SPEC.md hay sequence diagram trong session này —
nếu phát hiện spec sai, dừng lại báo tôi.
Đọc @SPEC.md.
Tôi vừa merge feature <feature>. Đọc:
- @docs/architecture/components.md (current diagram)
- @docs/architecture/sequences/<feature-slug>.md
- @<files đã thay đổi - dán paths hoặc "git diff main..HEAD" output>
So sánh code đã merge với components.md hiện tại:
1. Component nào mới? Component nào bị xóa? Edge nào mới?
2. Liệt kê drift cụ thể.
3. Đề xuất diff cho components.md để khớp lại.
4. Đề xuất 1 row mới cho "Update log" với PR link.
5. Dừng, đợi tôi review trước khi áp dụng.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment