#!/usr/bin/env bash # context-load — Gather project context for a Claude Code session # # Walks from cwd upward collecting key index files. # Outputs structured context to stdout, suitable for --append-system-prompt. # # What it loads: # - Every CLAUDE.md found walking up from cwd (top-down order) # - Directory tree (depth 3) from each CLAUDE.md location # - Every CONTEXT.md found walking up from cwd (top-down order) # - Every MEMORY.md found walking up from cwd (top-down order) # - Every BESTPRACTICES.md found walking up from cwd (top-down order) # - All *.md files in cwd # - git-status-report from the highest-level CLAUDE.md dir set -uo pipefail TREE_DEPTH=3 # --- Helpers --- emit_header() { echo "" echo "================================================================" echo "=== $1" echo "================================================================" echo "" } emit_file() { local filepath="$1" emit_header "FILE: $filepath" cat "$filepath" echo "" } emit_tree() { local dir="$1" emit_header "TREE: $dir (depth $TREE_DEPTH)" tree -L "$TREE_DEPTH" --charset utf-8 -I '.git|node_modules|__pycache__|.venv|venv|customers' "$dir" 2>/dev/null \ || find "$dir" -maxdepth "$TREE_DEPTH" -not -path '*/.git/*' -not -path '*/.git' -not -path '*/customers/*' -not -path '*/customers' | sort echo "" } strip_ansi() { sed 's/\x1b\[[0-9;]*m//g' } # --- Discovery: walk from cwd upward --- discover_upward() { local dir="$PWD" local dirs=() while [[ "$dir" != "/" ]]; do dirs+=("$dir") dir="$(dirname "$dir")" done # Return in top-down order (reverse — highest ancestor first) for ((i=${#dirs[@]}-1; i>=0; i--)); do echo "${dirs[$i]}" done } # Collect dirs that have key index files claude_dirs=() context_dirs=() memory_dirs=() bestpractices_dirs=() all_hierarchy_dirs=() while IFS= read -r dir; do all_hierarchy_dirs+=("$dir") [[ -f "$dir/CLAUDE.md" ]] && claude_dirs+=("$dir") [[ -f "$dir/CONTEXT.md" ]] && context_dirs+=("$dir") [[ -f "$dir/MEMORY.md" ]] && memory_dirs+=("$dir") [[ -f "$dir/BESTPRACTICES.md" ]] && bestpractices_dirs+=("$dir") done < <(discover_upward) if [[ ${#claude_dirs[@]} -eq 0 ]]; then echo "# No CLAUDE.md found in directory hierarchy" >&2 echo "# Searched from: $PWD" >&2 fi # --- Output: Claude profile --- profile_dir="${CLAUDE_CONFIG_DIR:-$HOME/.claude}" profile_name="$(basename "$profile_dir")" if [[ "$profile_name" == ".claude" ]]; then emit_header "CLAUDE PROFILE: default (~/.claude)" else emit_header "CLAUDE PROFILE: $profile_name ($profile_dir)" fi # --- Output: CLAUDE.md files (top-down) + tree from each --- # Track files we've already emitted to avoid duplicates declare -A emitted_files for dir in "${claude_dirs[@]}"; do # Resolve symlinks for dedup — two paths might point to the same file real_path="$(readlink -f "$dir/CLAUDE.md")" if [[ -z "${emitted_files[$real_path]:-}" ]]; then emit_file "$dir/CLAUDE.md" emitted_files["$real_path"]=1 fi emit_tree "$dir" done # --- Output: CONTEXT.md files (top-down) --- for dir in "${context_dirs[@]}"; do real_path="$(readlink -f "$dir/CONTEXT.md")" if [[ -z "${emitted_files[$real_path]:-}" ]]; then emit_file "$dir/CONTEXT.md" emitted_files["$real_path"]=1 fi done # --- Output: MEMORY.md files (top-down) --- for dir in "${memory_dirs[@]}"; do real_path="$(readlink -f "$dir/MEMORY.md")" if [[ -z "${emitted_files[$real_path]:-}" ]]; then emit_file "$dir/MEMORY.md" emitted_files["$real_path"]=1 fi done # --- Output: BESTPRACTICES.md files (top-down) --- for dir in "${bestpractices_dirs[@]}"; do real_path="$(readlink -f "$dir/BESTPRACTICES.md")" if [[ -z "${emitted_files[$real_path]:-}" ]]; then emit_file "$dir/BESTPRACTICES.md" emitted_files["$real_path"]=1 fi done # --- Output: all *.md files in cwd --- if compgen -G "$PWD"/*.md > /dev/null 2>&1; then for md_file in "$PWD"/*.md; do [[ -f "$md_file" ]] || continue real_path="$(readlink -f "$md_file")" if [[ -z "${emitted_files[$real_path]:-}" ]]; then emit_file "$md_file" emitted_files["$real_path"]=1 fi done fi # --- Output: git status report --- if [[ ${#claude_dirs[@]} -gt 0 ]]; then highest_dir="${claude_dirs[0]}" git_status_report="$(command -v git-status-report 2>/dev/null || true)" if [[ -n "$git_status_report" ]]; then emit_header "GIT STATUS: $highest_dir" "$git_status_report" "$highest_dir" 2>/dev/null | strip_ansi || true fi fi