#!/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: # - Paths to every CLAUDE.md found walking up from cwd (not contents — Claude Code loads those natively) # - Paths to every project CLAUDE.md found under each CLAUDE.md directory (for project discovery) # - 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) set -uo pipefail # --- Helpers --- emit_header() { echo "" echo "================================================================" echo "=== $1" echo "================================================================" echo "" } emit_file() { local filepath="$1" emit_header "FILE: $filepath" cat "$filepath" echo "" } # Extract description from ABOUT.md in the same directory as a CLAUDE.md get_project_description() { local claude_path="$1" local dir dir="$(dirname "$claude_path")" local about="$dir/ABOUT.md" if [[ -f "$about" ]]; then sed -n 's/^description:[[:space:]]*//p' "$about" | head -1 fi } # --- 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=() while IFS= read -r dir; do [[ -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 paths (not contents — Claude Code loads those natively) --- # Also discover project CLAUDE.md files under each directory for project listing # Track files we've already emitted to avoid duplicates declare -A emitted_files emit_header "CLAUDE.md FILES" for dir in "${claude_dirs[@]}"; do real_path="$(readlink -f "$dir/CLAUDE.md")" if [[ -z "${emitted_files[$real_path]:-}" ]]; then desc="$(get_project_description "$dir/CLAUDE.md")" if [[ -n "$desc" ]]; then echo "- $dir/CLAUDE.md — $desc" else echo "- $dir/CLAUDE.md" fi emitted_files["$real_path"]=1 fi done # Discover project CLAUDE.md files (depth 2-4 under each CLAUDE.md dir) # This replaces the old tree output — gives Claude a fresh project listing project_claudes=() for dir in "${claude_dirs[@]}"; do while IFS= read -r f; do real_path="$(readlink -f "$f")" if [[ -z "${emitted_files[$real_path]:-}" ]]; then project_claudes+=("$f") emitted_files["$real_path"]=1 fi done < <(find "$dir" -mindepth 2 -maxdepth 4 -name CLAUDE.md -not -path '*/.git/*' -not -path '*/node_modules/*' -not -path '*/__pycache__/*' 2>/dev/null | sort) done if [[ ${#project_claudes[@]} -gt 0 ]]; then echo "" echo "Project CLAUDE.md files:" for f in "${project_claudes[@]}"; do desc="$(get_project_description "$f")" if [[ -n "$desc" ]]; then echo "- $f — $desc" else echo "- $f" fi done fi echo "" # --- 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