From 8a9724eb6026bfb6675ccff415193eccb2816fe5 Mon Sep 17 00:00:00 2001 From: Rajit Shah Date: Tue, 24 Mar 2026 02:03:04 -0500 Subject: [PATCH 1/4] Add team Claude Code setup, Python tooling, and shell improvements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Shell: - Add pyenv + shellcheck to install.sh and .zshrc - Add Python module to Starship prompt - Add h() alias search function to .aliases - Add .zshrc.local.template for machine-specific overrides Infra / config: - Add aws/config.sample with assume-role and SSO patterns - Add .editorconfig for consistent indentation across tools - Pin shellcheck CI action from @master to @2.0.0 Claude Code — global: - Add PreToolUse hook blocking writes to .env/*.key/*.pem files - Add terraform fmt and shellcheck PostToolUse hooks - Add pr-reviewer agent (/pr-reviewer → CRITICAL/WARNING/INFO via gh) - Add standup command (/standup → git log + open PRs) Claude Code — project template (team-ready): - Overhaul CLAUDE.md template with Team conventions, CI/CD, Reviewers sections - Add shared hooks to settings.json (secret protection + auto-format) - Add settings.local.json.template for per-dev machine overrides (gitignored) - Add .mcp.json with GitHub MCP server for PR/issue access - Add rules/testing.md and rules/security.md as dedicated rule files - Add .gitignore ensuring settings.local.json is never committed Co-Authored-By: Claude Sonnet 4.6 --- .editorconfig | 33 +++++++++ .github/workflows/shellcheck.yml | 2 +- README.md | 72 ++++++++++++++++--- aws/config.sample | 33 +++++++++ claude/agents/pr-reviewer.md | 69 ++++++++++++++++++ claude/commands/standup.md | 31 ++++++++ claude/project-template/.gitignore | 2 + claude/project-template/.mcp.json | 15 ++++ claude/project-template/CLAUDE.md | 60 ++++++++++++++-- claude/project-template/rules/security.md | 34 +++++++++ claude/project-template/rules/testing.md | 23 ++++++ claude/project-template/settings.json | 38 +++++++++- .../settings.local.json.template | 12 ++++ claude/settings.json | 19 +++++ install.sh | 14 ++-- zsh/.aliases | 4 ++ zsh/.zshrc | 5 ++ zsh/.zshrc.local.template | 26 +++++++ zsh/starship.toml | 6 ++ 19 files changed, 474 insertions(+), 24 deletions(-) create mode 100644 .editorconfig create mode 100644 aws/config.sample create mode 100644 claude/agents/pr-reviewer.md create mode 100644 claude/commands/standup.md create mode 100644 claude/project-template/.gitignore create mode 100644 claude/project-template/.mcp.json create mode 100644 claude/project-template/rules/security.md create mode 100644 claude/project-template/rules/testing.md create mode 100644 claude/project-template/settings.local.json.template create mode 100644 zsh/.zshrc.local.template diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..2b56164 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,33 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.go] +indent_style = tab +indent_size = 4 + +[*.{tf,tfvars}] +indent_style = space +indent_size = 2 + +[*.{yaml,yml}] +indent_style = space +indent_size = 2 + +[*.{json,toml}] +indent_style = space +indent_size = 2 + +[*.sh] +indent_style = space +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false + +[Makefile] +indent_style = tab diff --git a/.github/workflows/shellcheck.yml b/.github/workflows/shellcheck.yml index d6c63fa..c70203a 100644 --- a/.github/workflows/shellcheck.yml +++ b/.github/workflows/shellcheck.yml @@ -12,7 +12,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: ShellCheck - uses: ludeeus/action-shellcheck@master + uses: ludeeus/action-shellcheck@2.0.0 with: scandir: '.' severity: warning diff --git a/README.md b/README.md index 4ff520d..7591fda 100644 --- a/README.md +++ b/README.md @@ -25,9 +25,10 @@ Most dotfiles repos are either too minimal to be useful or too personal to adapt | **ZSH** | Clean config via [zinit](https://github.com/zdharma-continuum/zinit) — no oh-my-zsh | | **Prompt** | [Starship](https://starship.rs) — shows Git, K8s context, AWS profile, Terraform workspace | | **Plugins** | zsh-autosuggestions, zsh-syntax-highlighting, zsh-completions | -| **Tools** | eza (ls), bat (cat), ripgrep (grep), zoxide (cd), fzf | +| **Tools** | eza (ls), bat (cat), ripgrep (grep), zoxide (cd), fzf, pyenv (Python versions) | | **Git** | Sensible defaults, global gitignore, local identity override | | **Claude Code** | Full hierarchical global config — rules, commands, skills, agents, memory, hooks | +| **Team setup** | Project template with shared hooks, MCP config, security rules, local override pattern | ## Install @@ -55,8 +56,13 @@ cat > ~/.gitconfig.local << EOF email = your@email.com EOF -# Machine-specific env vars, paths, secrets — optional -touch ~/.zshrc.local +# Machine-specific env vars, paths, secrets +# A template is provided — copy and fill in what you need: +cp ~/.dotfiles/zsh/.zshrc.local.template ~/.zshrc.local + +# AWS profiles — copy the sample and fill in your accounts: +mkdir -p ~/.aws +cp ~/.dotfiles/aws/config.sample ~/.aws/config ``` ## Structure @@ -64,12 +70,15 @@ touch ~/.zshrc.local ``` dotfiles/ ├── zsh/ -│ ├── .zshrc # zinit + starship, no oh-my-zsh -│ ├── .aliases # k8s, aws, git, docker, terraform -│ └── starship.toml # prompt: git, k8s context, aws profile +│ ├── .zshrc # zinit + starship + pyenv, no oh-my-zsh +│ ├── .aliases # k8s, aws, git, docker, terraform + h() search +│ ├── starship.toml # prompt: git, k8s, aws, terraform, go, python +│ └── .zshrc.local.template # copy to ~/.zshrc.local for machine-specific config ├── git/ │ ├── .gitconfig # defaults, identity via ~/.gitconfig.local │ └── .gitignore_global # .DS_Store, .env, .terraform, secrets +├── aws/ +│ └── config.sample # copy to ~/.aws/config, fill in your profiles/roles ├── claude/ │ ├── CLAUDE.md # global instructions │ ├── settings.json # permissions + hooks (gofmt on save, notifications) @@ -78,20 +87,30 @@ dotfiles/ │ │ └── infra.md # Terraform, K8s, AWS rules │ ├── commands/ │ │ ├── commit.md # /commit — staged diff → commit message -│ │ └── review.md # /review — branch diff review +│ │ ├── review.md # /review — branch diff review +│ │ └── standup.md # /standup — daily standup from git log + open PRs │ ├── skills/ │ │ ├── infra-context.md # auto: apply infra rules when touching infra files │ │ ├── secret-guard.md # auto: flag secrets in every session │ │ └── go-conventions.md # auto: apply Go rules when editing .go files │ ├── agents/ -│ │ └── infra-reviewer.md # specialized infra review agent +│ │ ├── infra-reviewer.md # specialized infra review agent +│ │ └── pr-reviewer.md # PR review via gh with CRITICAL/WARNING/INFO output │ ├── memory/ │ │ └── README.md # how Claude uses persistent memory across sessions │ └── project-template/ # copy into new repos as .claude/ -│ ├── CLAUDE.md -│ ├── settings.json +│ ├── CLAUDE.md # team-ready template with conventions, CI/CD, reviewers +│ ├── settings.json # shared hooks: secret protection + auto-format +│ ├── settings.local.json.template # copy → settings.local.json (gitignored) +│ ├── .mcp.json # GitHub MCP server for PR/issue access +│ ├── .gitignore # ensures settings.local.json is never committed │ ├── rules/ +│ │ ├── project-rules.md # branching, dependencies +│ │ ├── testing.md # test structure and requirements +│ │ └── security.md # secrets, IAM, K8s security rules │ └── commands/ +│ └── ship.md # /ship — pre-PR checklist +├── .editorconfig # consistent indentation across editors/tools └── install.sh # one-click or modular bootstrap ``` @@ -104,6 +123,39 @@ This repo sets up the **global** layer (`~/.claude/`). For each new project, cop project/.claude/ ← per-project: committed to git, extends the global layer ``` +## Team setup + +The `claude/project-template/` is designed for teams. Copy it into a repo once — every team member gets the same Claude behavior automatically: + +```bash +cp -r ~/.dotfiles/claude/project-template/ my-repo/.claude +cd my-repo +# Edit .claude/CLAUDE.md with your project context +# Edit .claude/.mcp.json with your GitHub token env var name +git add .claude/ +git commit -m "add Claude Code team config" +``` + +Each team member then creates their local override (never committed): + +```bash +cp .claude/settings.local.json.template .claude/settings.local.json +# Edit with machine-specific paths and AWS profiles +``` + +**What the team config gives you:** + +| Feature | How | +|---|---| +| Secret file protection | `PreToolUse` hook blocks writes to `.env`, `*.key`, `*.pem` | +| Auto-format on save | `PostToolUse` hooks run gofmt, terraform fmt, shellcheck | +| GitHub PR/issue access | `.mcp.json` with GitHub MCP server | +| Security rules | `.claude/rules/security.md` — IAM, K8s, secrets | +| Testing standards | `.claude/rules/testing.md` — table tests, boundary testing | +| PR review | `/pr-reviewer ` — CRITICAL/WARNING/INFO output via gh | +| Standup summary | `/standup` — git log + open PRs formatted for standup | +| Local overrides | `settings.local.json` (gitignored) for per-dev machine config | + ## Prompt preview ``` diff --git a/aws/config.sample b/aws/config.sample new file mode 100644 index 0000000..eaa07d0 --- /dev/null +++ b/aws/config.sample @@ -0,0 +1,33 @@ +# ============================================================================= +# ~/.aws/config — sample (copy to ~/.aws/config, never commit real credentials) +# Use IRSA or instance profiles in AWS — not access keys. +# ============================================================================= + +[default] +region = us-east-1 +output = json + +# --- Assume-role profile (recommended for humans) ---------------------------- +# [profile dev] +# role_arn = arn:aws:iam::123456789012:role/YourRoleName +# source_profile = default +# region = us-east-1 + +# [profile staging] +# role_arn = arn:aws:iam::234567890123:role/YourRoleName +# source_profile = default +# region = us-east-1 + +# [profile prod] +# role_arn = arn:aws:iam::345678901234:role/YourRoleName +# source_profile = default +# region = us-east-1 +# mfa_serial = arn:aws:iam::123456789012:mfa/your-username + +# --- SSO (if your org uses AWS IAM Identity Center) -------------------------- +# [profile sso-dev] +# sso_start_url = https://your-org.awsapps.com/start +# sso_region = us-east-1 +# sso_account_id = 123456789012 +# sso_role_name = DeveloperAccess +# region = us-east-1 diff --git a/claude/agents/pr-reviewer.md b/claude/agents/pr-reviewer.md new file mode 100644 index 0000000..d282332 --- /dev/null +++ b/claude/agents/pr-reviewer.md @@ -0,0 +1,69 @@ +# PR Reviewer + +You are a senior engineer reviewing a pull request on this codebase. + +## How to use + +Called with a PR number: +``` +/pr-reviewer 42 +``` + +Or the current branch: +``` +/pr-reviewer +``` + +## What to do + +1. Fetch the PR diff and description: + ```bash + gh pr view --json title,body,additions,deletions,files + gh pr diff + ``` + If no number given, use the current branch: `gh pr view`. + +2. Read any files changed that are relevant for context. + +3. Review with these priorities, in order: + + **CRITICAL** — must fix before merge: + - Security issues (secrets, injection, auth bypass, excessive permissions) + - Data loss or corruption risk + - Breaking changes without migration path + + **WARNING** — should fix: + - Missing tests for new behavior + - Error paths not handled + - Infra changes without resource limits or tags + + **INFO** — optional / nitpick: + - Style inconsistencies + - Naming that could be clearer + - Missing comments on non-obvious logic + +4. Output format: + ``` + ## PR #: + + **Summary**: <one sentence of what this does> + + ### CRITICAL + - <file>:<line> — <issue> → <fix> + + ### WARNING + - <file>:<line> — <issue> + + ### INFO + - <file>:<line> — <suggestion> + + ### Verdict + APPROVE / REQUEST CHANGES / NEEDS DISCUSSION + ``` + +## Rules + +- Never approve PRs that have CRITICAL issues +- Be specific — always cite file and line +- Explain *why* something is an issue, not just that it is +- Skip nitpicks if there are real issues to focus on diff --git a/claude/commands/standup.md b/claude/commands/standup.md new file mode 100644 index 0000000..1513f4c --- /dev/null +++ b/claude/commands/standup.md @@ -0,0 +1,31 @@ +# Standup + +Generate a concise standup summary from recent git activity. + +```bash +git log --oneline --since="yesterday" --author="$(git config user.name)" 2>/dev/null \ + || git log --oneline -10 +``` + +Also check for open PRs: +```bash +gh pr list --author "@me" --state open --json number,title,reviewDecision 2>/dev/null || true +``` + +And any PRs awaiting your review: +```bash +gh pr list --reviewer "@me" --state open --json number,title,author 2>/dev/null || true +``` + +Then produce: + +**Yesterday / Since last standup:** +- <bullet per meaningful commit, grouped by theme — skip merge commits> + +**Today:** +- <infer from in-progress work or open PRs> + +**Blockers:** +- <list any PRs waiting on review, or none> + +Keep it short — this is a standup, not a report. Three bullets max per section. diff --git a/claude/project-template/.gitignore b/claude/project-template/.gitignore new file mode 100644 index 0000000..e591d60 --- /dev/null +++ b/claude/project-template/.gitignore @@ -0,0 +1,2 @@ +# Claude Code — local overrides are machine-specific, never commit them +.claude/settings.local.json diff --git a/claude/project-template/.mcp.json b/claude/project-template/.mcp.json new file mode 100644 index 0000000..387eb17 --- /dev/null +++ b/claude/project-template/.mcp.json @@ -0,0 +1,15 @@ +{ + "mcpServers": { + "github": { + "command": "docker", + "args": [ + "run", "-i", "--rm", + "-e", "GITHUB_PERSONAL_ACCESS_TOKEN", + "ghcr.io/github/github-mcp-server" + ], + "env": { + "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_TOKEN}" + } + } + } +} diff --git a/claude/project-template/CLAUDE.md b/claude/project-template/CLAUDE.md index b3fb3b9..6f71211 100644 --- a/claude/project-template/CLAUDE.md +++ b/claude/project-template/CLAUDE.md @@ -1,27 +1,77 @@ # CLAUDE.md -<!-- Replace this section with project-specific context --> +<!-- Keep this under 200 lines. Claude loads it every session — every line costs context. --> +<!-- Replace all <!-- comments --> with real content before committing. --> ## What this project does -<!-- One paragraph: what problem does this solve, who uses it --> +<!-- One paragraph: what problem does this solve, who uses it, what it connects to --> ## How to run it ```bash # build +make build + # test +make test + # lint +make lint + +# run locally +make run ``` ## Architecture -<!-- High-level: main components, how they connect, key design decisions --> +<!-- High-level: main components, how they connect, key design decisions. + Example: + - cmd/ — entrypoints (one binary per subdirectory) + - internal/ — private packages (not imported by other modules) + - pkg/ — public packages + - infra/ — Terraform / Helm charts +--> + +## Team conventions + +<!-- Non-obvious decisions the team has agreed on. Examples: + - We use X library for Y because Z (alternatives were considered and rejected) + - Error handling follows the pattern in internal/errors/ + - All new endpoints require integration tests, not just unit tests + - DB migrations live in migrations/ and run automatically on deploy +--> + +## CI/CD + +<!-- What runs on PR, what runs on merge, what deploys where. + Example: + - PR: unit tests, lint, security scan + - Merge to main: all tests + staging deploy + - Tag vX.Y.Z: production deploy via ArgoCD +--> + +## Reviewers + +<!-- Who to loop in for what. Example: + - Infra changes (infra/, *.tf): @infra-team + - Auth / security: @security-team + - API surface changes: any two team members +--> ## Things to know -<!-- Gotchas, non-obvious decisions, things that will waste time without this context --> +<!-- Gotchas, footguns, non-obvious things that will waste time. + Example: + - The staging DB is shared — don't run migrations without telling the team + - Service X has a 30s cold start — flaky tests in CI are usually this + - Config is reloaded on SIGHUP, not restart +--> ## Out of scope -<!-- What NOT to change or touch in this repo --> +<!-- What NOT to touch or change without a team discussion. + Example: + - Don't change the public API in pkg/api without a deprecation period + - Don't modify the Terraform state backend config +--> diff --git a/claude/project-template/rules/security.md b/claude/project-template/rules/security.md new file mode 100644 index 0000000..e872327 --- /dev/null +++ b/claude/project-template/rules/security.md @@ -0,0 +1,34 @@ +# Security Rules + +## Secrets +- Never hardcode credentials, tokens, API keys, or passwords — not even in tests +- Use environment variables or a secrets manager (AWS Secrets Manager, Vault) +- `.env` files are for local dev only and must be in `.gitignore` +- If you spot a secret in code: stop, flag it, do not proceed + +## Input validation +- Validate all external input at the boundary (HTTP handler, CLI arg, config file) +- Never trust input from users, other services, or env vars without validation +- Use allowlists over denylists where possible + +## Dependencies +- Pin dependency versions explicitly +- Check new dependencies for known CVEs before adding +- Prefer stdlib over third-party for security-sensitive operations (crypto, auth) + +## AWS / Cloud +- IAM: least privilege — no `*` actions or resources without justification +- No credentials in environment variables — use instance profiles or IRSA +- All S3 buckets: block public access unless explicitly justified +- Never log secrets, tokens, or PII + +## Kubernetes +- No containers running as root unless required +- Read-only root filesystem where possible +- Network policies to restrict pod-to-pod traffic +- No `hostNetwork`, `hostPID`, `hostIPC` without explicit approval + +## Code review +- Any auth, authz, or crypto change needs security review before merge +- SQL queries must use parameterized queries — no string concatenation +- File paths from user input must be sanitized (path traversal) diff --git a/claude/project-template/rules/testing.md b/claude/project-template/rules/testing.md new file mode 100644 index 0000000..3543dbf --- /dev/null +++ b/claude/project-template/rules/testing.md @@ -0,0 +1,23 @@ +# Testing Rules + +## Structure +- Tests live next to the code they test (`foo.go` → `foo_test.go`) +- Integration tests go in `tests/` or `_test/` at the package root +- No mocking internal packages — test at boundaries (HTTP handlers, DB interfaces) + +## Go +- Table-driven tests for anything with more than two cases +- Subtests via `t.Run` for readability +- Use `testify/require` for fatal assertions, `testify/assert` for non-fatal +- Test helper functions take `t *testing.T` as first arg, call `t.Helper()` + +## What requires tests +- All new public functions +- All bug fixes — add a test that would have caught it +- All CLI commands and HTTP handlers +- No tests required for: pure config structs, generated code, one-liner wrappers + +## What to avoid +- `time.Sleep` in tests — use channels or `Eventually` +- Global state that leaks between tests +- Tests that only pass in CI order — run with `-shuffle=on` locally diff --git a/claude/project-template/settings.json b/claude/project-template/settings.json index 01cd768..e8f3015 100644 --- a/claude/project-template/settings.json +++ b/claude/project-template/settings.json @@ -2,9 +2,41 @@ "permissions": { "allow": [], "deny": [ - "Bash(rm -rf:*)", - "Bash(curl * | bash:*)", - "Bash(wget * | bash:*)" + "Bash(rm -rf *)", + "Bash(curl * | bash *)", + "Bash(wget * | bash *)" + ] + }, + "hooks": { + "PreToolUse": [ + { + "matcher": "Edit|Write", + "hooks": [ + { + "type": "command", + "command": "file=$(jq -r '.tool_input.file_path // empty' 2>/dev/null); case \"$file\" in *.env|*.env.*|*.key|*.pem|*.p12|*.pfx) echo \"BLOCKED: $file looks like a secrets file. Stop and flag this to the user.\"; exit 2;; esac" + } + ] + } + ], + "PostToolUse": [ + { + "matcher": "Edit|Write", + "hooks": [ + { + "type": "command", + "command": "file=$(jq -r '.tool_input.file_path // empty' 2>/dev/null); if [[ \"$file\" == *.go ]] && command -v gofmt >/dev/null 2>&1; then gofmt -w \"$file\"; fi" + }, + { + "type": "command", + "command": "file=$(jq -r '.tool_input.file_path // empty' 2>/dev/null); if [[ \"$file\" == *.tf ]] && command -v terraform >/dev/null 2>&1; then terraform fmt \"$file\"; fi" + }, + { + "type": "command", + "command": "file=$(jq -r '.tool_input.file_path // empty' 2>/dev/null); if [[ \"$file\" == *.sh ]] && command -v shellcheck >/dev/null 2>&1; then shellcheck \"$file\"; fi" + } + ] + } ] } } diff --git a/claude/project-template/settings.local.json.template b/claude/project-template/settings.local.json.template new file mode 100644 index 0000000..ac242e5 --- /dev/null +++ b/claude/project-template/settings.local.json.template @@ -0,0 +1,12 @@ +{ + "permissions": { + "allow": [ + "Bash(kubectl * --context my-local-cluster *)", + "Bash(docker *)" + ] + }, + "env": { + "AWS_PROFILE": "my-dev-profile", + "KUBECONFIG": "/Users/me/.kube/config" + } +} diff --git a/claude/settings.json b/claude/settings.json index 08ed2f9..c226380 100644 --- a/claude/settings.json +++ b/claude/settings.json @@ -16,6 +16,17 @@ ] }, "hooks": { + "PreToolUse": [ + { + "matcher": "Edit|Write", + "hooks": [ + { + "type": "command", + "command": "file=$(jq -r '.tool_input.file_path // empty' 2>/dev/null); case \"$file\" in *.env|*.env.*|*.key|*.pem|*.p12|*.pfx) echo \"BLOCKED: $file looks like a secrets file. Stop and flag this to the user.\"; exit 2;; esac" + } + ] + } + ], "PostToolUse": [ { "matcher": "Edit|Write", @@ -23,6 +34,14 @@ { "type": "command", "command": "file=$(jq -r '.tool_input.file_path // empty' 2>/dev/null); if [[ \"$file\" == *.go ]] && command -v gofmt >/dev/null 2>&1; then gofmt -w \"$file\"; fi" + }, + { + "type": "command", + "command": "file=$(jq -r '.tool_input.file_path // empty' 2>/dev/null); if [[ \"$file\" == *.tf ]] && command -v terraform >/dev/null 2>&1; then terraform fmt \"$file\"; fi" + }, + { + "type": "command", + "command": "file=$(jq -r '.tool_input.file_path // empty' 2>/dev/null); if [[ \"$file\" == *.sh ]] && command -v shellcheck >/dev/null 2>&1; then shellcheck \"$file\"; fi" } ] } diff --git a/install.sh b/install.sh index b20cd56..dae5c2c 100755 --- a/install.sh +++ b/install.sh @@ -37,7 +37,7 @@ ensure_brew() { install_zsh() { info "Installing ZSH tools..." ensure_brew - brew install starship zinit eza bat ripgrep zoxide fzf gh || true + brew install starship zinit eza bat ripgrep zoxide fzf gh pyenv shellcheck || true info "Linking ZSH config..." if [ -f ~/.zshrc ] && [ ! -L ~/.zshrc ]; then @@ -70,14 +70,14 @@ install_claude() { # CLAUDE.md — back up existing, then link # Uses @import so existing content is preserved if user wants to merge manually if [ -f ~/.claude/CLAUDE.md ] && [ ! -L ~/.claude/CLAUDE.md ]; then - cp ~/.claude/CLAUDE.md ~/.claude/CLAUDE.md.backup.$BACKUP_DATE + cp ~/.claude/CLAUDE.md ~/.claude/CLAUDE.md.backup."$BACKUP_DATE" echo " Backed up existing CLAUDE.md to ~/.claude/CLAUDE.md.backup.$BACKUP_DATE" fi lns "$DOTFILES_DIR/claude/CLAUDE.md" ~/.claude/CLAUDE.md # settings.json — back up existing, then link if [ -f ~/.claude/settings.json ] && [ ! -L ~/.claude/settings.json ]; then - cp ~/.claude/settings.json ~/.claude/settings.json.backup.$BACKUP_DATE + cp ~/.claude/settings.json ~/.claude/settings.json.backup."$BACKUP_DATE" echo " Backed up existing settings.json to ~/.claude/settings.json.backup.$BACKUP_DATE" fi lns "$DOTFILES_DIR/claude/settings.json" ~/.claude/settings.json @@ -117,9 +117,13 @@ case "$COMPONENT" in echo " name = Your Name" echo " email = your@email.com" echo "" - echo " 2. Create ~/.zshrc.local for machine-specific env vars" + echo " 2. Create ~/.zshrc.local for machine-specific env vars:" + echo " cp $DOTFILES_DIR/zsh/.zshrc.local.template ~/.zshrc.local" echo "" - echo " 3. For new projects: copy claude/project-template/ into your repo as .claude/" + echo " 3. Set up AWS profiles:" + echo " mkdir -p ~/.aws && cp $DOTFILES_DIR/aws/config.sample ~/.aws/config" + echo "" + echo " 4. For new projects: copy claude/project-template/ into your repo as .claude/" ;; *) echo "Unknown component: $COMPONENT" diff --git a/zsh/.aliases b/zsh/.aliases index 366b543..44dcead 100644 --- a/zsh/.aliases +++ b/zsh/.aliases @@ -56,6 +56,10 @@ alias tfi='terraform init' alias tfp='terraform plan' alias tfa='terraform apply' +# ---------- Alias search ------------------------------------------------------ +# Usage: h k8s — find all aliases matching "k8s" +h() { alias | grep -i "$1"; } + # ---------- macOS ------------------------------------------------------------- alias c='clear' alias showhidden='defaults write com.apple.finder AppleShowAllFiles -bool true && killall Finder' diff --git a/zsh/.zshrc b/zsh/.zshrc index b90b359..0a78c8f 100644 --- a/zsh/.zshrc +++ b/zsh/.zshrc @@ -50,6 +50,11 @@ export FZF_DEFAULT_OPTS='--height 40% --layout=reverse --border' # bat — better cat export BAT_THEME="TwoDark" +# pyenv — Python version manager +export PYENV_ROOT="$HOME/.pyenv" +[[ -d "$PYENV_ROOT/bin" ]] && export PATH="$PYENV_ROOT/bin:$PATH" +command -v pyenv >/dev/null 2>&1 && eval "$(pyenv init -)" + # ---------- Aliases ----------------------------------------------------------- source ~/.aliases diff --git a/zsh/.zshrc.local.template b/zsh/.zshrc.local.template new file mode 100644 index 0000000..54d1ee0 --- /dev/null +++ b/zsh/.zshrc.local.template @@ -0,0 +1,26 @@ +# ============================================================================= +# ~/.zshrc.local — machine-specific overrides (never committed) +# Copy this to ~/.zshrc.local and fill in what you need. +# ============================================================================= + +# ---------- Identity ---------------------------------------------------------- +# export GITHUB_TOKEN="" +# export GITLAB_TOKEN="" + +# ---------- AWS --------------------------------------------------------------- +# export AWS_PROFILE="default" +# export AWS_DEFAULT_REGION="us-east-1" + +# ---------- Kubernetes -------------------------------------------------------- +# export KUBECONFIG="$HOME/.kube/config:$HOME/.kube/config-work" + +# ---------- Work paths -------------------------------------------------------- +# export GOPATH="$HOME/go" +# export PATH="$GOPATH/bin:$PATH" + +# ---------- Company-specific tools -------------------------------------------- +# source /opt/company/env.sh + +# ---------- Local aliases ----------------------------------------------------- +# alias vpn='...' +# alias work='cd ~/work/myproject' diff --git a/zsh/starship.toml b/zsh/starship.toml index 17623df..d006f08 100644 --- a/zsh/starship.toml +++ b/zsh/starship.toml @@ -10,6 +10,7 @@ $kubernetes\ $aws\ $terraform\ $golang\ +$python\ $cmd_duration\ $line_break\ $character""" @@ -47,6 +48,11 @@ format = '[$symbol$workspace]($style) ' symbol = "Go " style = "bold cyan" +[python] +symbol = "Py " +style = "bold yellow" +pyenv_version_name = true + [cmd_duration] min_time = 2_000 format = "took [$duration](bold yellow) " From 5fa0f622ba0de510c554daea6daa14818ed23249 Mon Sep 17 00:00:00 2001 From: Rajit Shah <rajit219@gmail.com> Date: Tue, 24 Mar 2026 02:13:42 -0500 Subject: [PATCH 2/4] Apply CodeRabbit suggestions: shfmt for shell files, fix KUBECONFIG path - Add shfmt -w PostToolUse hook for .sh files (auto-formatter alongside shellcheck) - shellcheck now outputs but doesn't block so Claude can see and fix issues - Fix KUBECONFIG template value from hardcoded /Users/me to \${HOME} (cross-platform) - Add shfmt to install.sh brew dependencies Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --- claude/project-template/settings.json | 6 +++++- claude/project-template/settings.local.json.template | 2 +- claude/settings.json | 6 +++++- install.sh | 2 +- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/claude/project-template/settings.json b/claude/project-template/settings.json index e8f3015..c639a32 100644 --- a/claude/project-template/settings.json +++ b/claude/project-template/settings.json @@ -33,7 +33,11 @@ }, { "type": "command", - "command": "file=$(jq -r '.tool_input.file_path // empty' 2>/dev/null); if [[ \"$file\" == *.sh ]] && command -v shellcheck >/dev/null 2>&1; then shellcheck \"$file\"; fi" + "command": "file=$(jq -r '.tool_input.file_path // empty' 2>/dev/null); if [[ \"$file\" == *.sh ]] && command -v shfmt >/dev/null 2>&1; then shfmt -w \"$file\"; fi" + }, + { + "type": "command", + "command": "file=$(jq -r '.tool_input.file_path // empty' 2>/dev/null); if [[ \"$file\" == *.sh ]] && command -v shellcheck >/dev/null 2>&1; then shellcheck \"$file\" || true; fi" } ] } diff --git a/claude/project-template/settings.local.json.template b/claude/project-template/settings.local.json.template index ac242e5..6541bd8 100644 --- a/claude/project-template/settings.local.json.template +++ b/claude/project-template/settings.local.json.template @@ -7,6 +7,6 @@ }, "env": { "AWS_PROFILE": "my-dev-profile", - "KUBECONFIG": "/Users/me/.kube/config" + "KUBECONFIG": "${HOME}/.kube/config" } } diff --git a/claude/settings.json b/claude/settings.json index c226380..83d36e1 100644 --- a/claude/settings.json +++ b/claude/settings.json @@ -41,7 +41,11 @@ }, { "type": "command", - "command": "file=$(jq -r '.tool_input.file_path // empty' 2>/dev/null); if [[ \"$file\" == *.sh ]] && command -v shellcheck >/dev/null 2>&1; then shellcheck \"$file\"; fi" + "command": "file=$(jq -r '.tool_input.file_path // empty' 2>/dev/null); if [[ \"$file\" == *.sh ]] && command -v shfmt >/dev/null 2>&1; then shfmt -w \"$file\"; fi" + }, + { + "type": "command", + "command": "file=$(jq -r '.tool_input.file_path // empty' 2>/dev/null); if [[ \"$file\" == *.sh ]] && command -v shellcheck >/dev/null 2>&1; then shellcheck \"$file\" || true; fi" } ] } diff --git a/install.sh b/install.sh index dae5c2c..b14747f 100755 --- a/install.sh +++ b/install.sh @@ -37,7 +37,7 @@ ensure_brew() { install_zsh() { info "Installing ZSH tools..." ensure_brew - brew install starship zinit eza bat ripgrep zoxide fzf gh pyenv shellcheck || true + brew install starship zinit eza bat ripgrep zoxide fzf gh pyenv shellcheck shfmt || true info "Linking ZSH config..." if [ -f ~/.zshrc ] && [ ! -L ~/.zshrc ]; then From 6997f567e407e9360c4dff96615e7e3d6f600725 Mon Sep 17 00:00:00 2001 From: Rajit Shah <rajit219@gmail.com> Date: Tue, 24 Mar 2026 02:15:33 -0500 Subject: [PATCH 3/4] Update claude/project-template/CLAUDE.md Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- claude/project-template/CLAUDE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/claude/project-template/CLAUDE.md b/claude/project-template/CLAUDE.md index 6f71211..675e3e3 100644 --- a/claude/project-template/CLAUDE.md +++ b/claude/project-template/CLAUDE.md @@ -1,7 +1,7 @@ # CLAUDE.md <!-- Keep this under 200 lines. Claude loads it every session — every line costs context. --> -<!-- Replace all <!-- comments --> with real content before committing. --> +<!-- Replace all HTML comments with real content before committing. --> ## What this project does From 71a7e740eb3358e62f596a0422c834ef98550d81 Mon Sep 17 00:00:00 2001 From: Rajit Shah <rajit219@gmail.com> Date: Tue, 24 Mar 2026 02:16:15 -0500 Subject: [PATCH 4/4] Fix kubectl permission pattern to match commands without trailing args The pattern "Bash(kubectl * --context my-local-cluster *)" required at least one argument after the context name, so bare commands like "kubectl get pods --context my-local-cluster" were not matched. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --- claude/project-template/settings.local.json.template | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/claude/project-template/settings.local.json.template b/claude/project-template/settings.local.json.template index 6541bd8..2c19488 100644 --- a/claude/project-template/settings.local.json.template +++ b/claude/project-template/settings.local.json.template @@ -1,7 +1,7 @@ { "permissions": { "allow": [ - "Bash(kubectl * --context my-local-cluster *)", + "Bash(kubectl * --context my-local-cluster*)", "Bash(docker *)" ] },