Created
October 31, 2025 04:20
-
-
Save Sammons/06cc075de4168e2f6f8fd26f5dc4ad5d to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| # Generate code outline per package for Claude Code | |
| # This script is called by the SessionStart hook to provide code structure context | |
| # Enable recursive glob patterns (**) | |
| shopt -s globstar | |
| # Check if CLAUDE_PROJECT_DIR is available | |
| if [ -z "$CLAUDE_PROJECT_DIR" ]; then | |
| echo "Error: CLAUDE_PROJECT_DIR not set" >&2 | |
| exit 1 | |
| fi | |
| # Check if code-outline command is available | |
| if ! command -v code-outline &> /dev/null; then | |
| echo "Error: code-outline command not found" >&2 | |
| exit 1 | |
| fi | |
| cd "$CLAUDE_PROJECT_DIR" || exit 1 | |
| # List of package directories to generate outlines for | |
| PACKAGES=( | |
| "packages/api" | |
| "packages/cdk" | |
| "packages/e2e" | |
| "packages/landing" | |
| "packages/ui" | |
| "packages/worker" | |
| ) | |
| # Generate outline for each package | |
| for package_dir in "${PACKAGES[@]}"; do | |
| if [ ! -d "$package_dir" ]; then | |
| echo "Warning: Package directory $package_dir does not exist, skipping" >&2 | |
| continue | |
| fi | |
| # Special handling for packages/api - split into src and tests | |
| if [ "$package_dir" = "packages/api" ]; then | |
| # Generate complete outline to temp file | |
| # code-outline requires a single quoted glob pattern, so call it for each extension | |
| TEMP_OUTLINE=$(mktemp) | |
| # Generate for .ts files | |
| code-outline "$package_dir/src/**/*.ts" --llmtext --depth 2 > "$TEMP_OUTLINE" 2>/dev/null || true | |
| # Append .tsx files (strip headers) | |
| code-outline "$package_dir/src/**/*.tsx" --llmtext --depth 2 2>/dev/null | \ | |
| sed '/^<Outline>$/,/^# Files$/d' | grep -v "^</Outline>$" >> "$TEMP_OUTLINE" 2>/dev/null || true | |
| # Append .js files (strip headers) | |
| code-outline "$package_dir/src/**/*.js" --llmtext --depth 2 2>/dev/null | \ | |
| sed '/^<Outline>$/,/^# Files$/d' | grep -v "^</Outline>$" >> "$TEMP_OUTLINE" 2>/dev/null || true | |
| # Close the outline | |
| echo "</Outline>" >> "$TEMP_OUTLINE" | |
| # Create src outline (excluding __tests__) | |
| SRC_OUTPUT="$package_dir/src/code-outline.md" | |
| mkdir -p "$package_dir/src" | |
| { | |
| echo "# Code Outline for $package_dir/src" | |
| echo "<!-- Generated on $(date '+%Y-%m-%d %H:%M:%S') -->" | |
| echo "<!-- Excludes __tests__ directories -->" | |
| echo "---" | |
| echo "" | |
| } > "$SRC_OUTPUT" | |
| # Filter out ALL test-related content using Python | |
| TEST_PATH_VARS_SRC=$(grep "^<p.*=.*/__tests__" "$TEMP_OUTLINE" | sed 's/=.*//' || true) | |
| python3 -c " | |
| import re | |
| test_path_vars = '''$TEST_PATH_VARS_SRC'''.strip().split('\n') if '''$TEST_PATH_VARS_SRC'''.strip() else [] | |
| with open('$TEMP_OUTLINE', 'r') as f: | |
| lines = f.readlines() | |
| in_test_file = False | |
| for line in lines: | |
| # Check if this is a file header line | |
| if re.match(r'^(<p\d+>/|packages/api/src/)', line) and '.ts' in line and re.search(r'\(\d+L\)', line): | |
| # Is this a test file? | |
| is_test = ('/__tests__/' in line or | |
| '.test.ts' in line or | |
| '.spec.ts' in line or | |
| any(line.startswith(pv + '/') for pv in test_path_vars if pv)) | |
| in_test_file = is_test | |
| if not is_test: | |
| print(line, end='') | |
| elif not in_test_file: | |
| # Skip path variable definitions for test directories | |
| if line.startswith('<p') and '/__tests__' in line: | |
| continue # Skip test path variable definitions | |
| print(line, end='') | |
| " >> "$SRC_OUTPUT" 2>/dev/null || true | |
| echo "Successfully generated code outline at $SRC_OUTPUT" >&2 | |
| # Create tests outline (only __tests__) | |
| TESTS_OUTPUT="$package_dir/src/__tests__/code-outline.md" | |
| mkdir -p "$package_dir/src/__tests__" | |
| { | |
| echo "# Code Outline for $package_dir/src/__tests__" | |
| echo "<!-- Generated on $(date '+%Y-%m-%d %H:%M:%S') -->" | |
| echo "---" | |
| echo "" | |
| } > "$TESTS_OUTPUT" | |
| # Extract only __tests__ files with all their content | |
| # First add the header | |
| head -20 "$TEMP_OUTLINE" | grep -E "^#|^<|^$" >> "$TESTS_OUTPUT" 2>/dev/null || true | |
| # Extract test files using Python for better parsing | |
| # First, identify which path variables point to test directories | |
| TEST_PATH_VARS=$(grep "^<p.*=.*/__tests__" "$TEMP_OUTLINE" | sed 's/=.*//' || true) | |
| python3 -c " | |
| import re | |
| test_path_vars = '''$TEST_PATH_VARS'''.strip().split('\n') if '''$TEST_PATH_VARS'''.strip() else [] | |
| with open('$TEMP_OUTLINE', 'r') as f: | |
| lines = f.readlines() | |
| in_test_file = False | |
| for line in lines: | |
| # Check if this is a file header line (contains path and line count like '(123L)') | |
| if re.match(r'^(<p\d+>/|packages/api/src/)', line) and '.ts' in line and re.search(r'\(\d+L\)', line): | |
| # Check if it's a test file by checking: | |
| # 1. Path contains /__tests__/ | |
| # 2. Starts with a test path variable | |
| # 3. Filename contains .test.ts or .spec.ts | |
| is_test = ('/__tests__/' in line or | |
| any(line.startswith(pv + '/') for pv in test_path_vars if pv) or | |
| '.test.ts' in line or '.spec.ts' in line) | |
| if is_test: | |
| in_test_file = True | |
| print(line, end='') | |
| else: | |
| in_test_file = False | |
| elif in_test_file: | |
| print(line, end='') | |
| " >> "$TESTS_OUTPUT" 2>/dev/null || true | |
| echo "</Outline>" >> "$TESTS_OUTPUT" | |
| echo "Successfully generated code outline at $TESTS_OUTPUT" >&2 | |
| # Cleanup | |
| rm -f "$TEMP_OUTLINE" | |
| [ -f "$package_dir/code-outline.md" ] && rm "$package_dir/code-outline.md" | |
| else | |
| # Standard handling for other packages | |
| OUTPUT_FILE="$package_dir/code-outline.md" | |
| # Generate the code outline | |
| { | |
| echo "# Code Outline for $package_dir" | |
| echo "<!-- Generated on $(date '+%Y-%m-%d %H:%M:%S') -->" | |
| echo "---" | |
| echo "" | |
| } > "$OUTPUT_FILE" | |
| # Run code-outline for this package | |
| if code-outline "$package_dir/**/*.ts" "$package_dir/**/*.tsx" "$package_dir/**/*.js" --llmtext --depth 2 >> "$OUTPUT_FILE" 2>/dev/null; then | |
| echo "Successfully generated code outline at $OUTPUT_FILE" >&2 | |
| else | |
| echo "Warning: code-outline for $package_dir completed with errors" >&2 | |
| fi | |
| fi | |
| done | |
| # Always exit successfully to not block session start | |
| exit 0 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| # Generates a recent git status and commit history report | |
| set -euo pipefail | |
| # Change to project directory | |
| cd "$(dirname "$0")/../.." || exit 1 | |
| OUTPUT_FILE="./.claude/recent-git.md" | |
| # Start the report | |
| { | |
| echo "# Recent Git Status" | |
| echo "Generated: $(date -u +"%Y-%m-%d %H:%M:%S UTC")" | |
| echo "" | |
| # Dirty (unstaged) files | |
| echo "## Dirty Files (Unstaged Changes)" | |
| echo "" | |
| if git diff --name-only | grep -q .; then | |
| git diff --name-only | while read -r file; do | |
| echo "- $file" | |
| done | |
| else | |
| echo "*No dirty files*" | |
| fi | |
| echo "" | |
| # Staged files | |
| echo "## Staged Files" | |
| echo "" | |
| if git diff --cached --name-only | grep -q .; then | |
| git diff --cached --name-only | while read -r file; do | |
| echo "- $file" | |
| done | |
| else | |
| echo "*No staged files*" | |
| fi | |
| echo "" | |
| # Last 5 commits | |
| echo "## Recent Commits (Last 5)" | |
| echo "" | |
| # Get the last 5 commit hashes | |
| git log -5 --format="%H" | while read -r commit; do | |
| # Get commit details | |
| title=$(git log -1 --format="%s" "$commit") | |
| body=$(git log -1 --format="%b" "$commit") | |
| author=$(git log -1 --format="%an" "$commit") | |
| date=$(git log -1 --format="%ci" "$commit") | |
| short_hash=$(git log -1 --format="%h" "$commit") | |
| echo "### $title" | |
| echo "" | |
| echo "**Commit:** \`$short_hash\` | **Author:** $author | **Date:** $date" | |
| echo "" | |
| # Show body if it exists | |
| if [ -n "$body" ]; then | |
| echo "$body" | |
| echo "" | |
| fi | |
| # Show modified files | |
| echo "**Modified files:**" | |
| echo "" | |
| git show --name-only --format="" "$commit" | while read -r file; do | |
| if [ -n "$file" ]; then | |
| echo "- $file" | |
| fi | |
| done | |
| echo "" | |
| done | |
| } > "$OUTPUT_FILE" | |
| # Output JSON for Claude Code hook system | |
| cat <<EOF | |
| { | |
| "hookSpecificOutput": { | |
| "hookEventName": "SessionStart", | |
| "additionalContext": "Recent git status and commit history saved to $OUTPUT_FILE" | |
| } | |
| } | |
| EOF |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "permissions": { | |
| "allow": [ | |
| "Task", | |
| "Bash", | |
| "Glob", | |
| "Grep", | |
| "Read", | |
| "Edit", | |
| "Write", | |
| "WebFetch", | |
| "WebSearch", | |
| "BashOutput", | |
| "KillShell", | |
| "AskUserQuestion", | |
| "Skill", | |
| "SlashCommand", | |
| "TodoWrite", | |
| "mcp__chrome-devtools__*" | |
| ] | |
| }, | |
| "hooks": { | |
| "SessionStart": [ | |
| { | |
| "matcher": "startup", | |
| "hooks": [ | |
| { | |
| "type": "command", | |
| "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/scripts/code-outline.sh" | |
| }, | |
| { | |
| "type": "command", | |
| "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/scripts/find-non-source-files.sh" | |
| }, | |
| { | |
| "type": "command", | |
| "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/scripts/recent-git.sh" | |
| } | |
| ] | |
| } | |
| ] | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment