Skip to content

Instantly share code, notes, and snippets.

@Sammons
Created October 31, 2025 04:20
Show Gist options
  • Select an option

  • Save Sammons/06cc075de4168e2f6f8fd26f5dc4ad5d to your computer and use it in GitHub Desktop.

Select an option

Save Sammons/06cc075de4168e2f6f8fd26f5dc4ad5d to your computer and use it in GitHub Desktop.
#!/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
#!/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
{
"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