ToonDex creates semantic folder indexes for LLM codebase navigation. It generates an index.toon file with concise summaries of each folder, ordered by importance.
93
Quality
93%
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
#!/bin/bash
# ToonDex Change Detection Script
# Runs on SessionStart. Outputs JSON with additionalContext for Claude.
# On the user's next message, Claude should propose running /redex.
set -e
# Configuration
INDEX_FILE="index.toon"
STATE_FILE=".toondex-state"
COOLDOWN_HOURS=24
SCORE_THRESHOLD=3
# Check cooldown FIRST (cheap check before expensive git operations)
if [ -f "$STATE_FILE" ]; then
LAST_PROPOSAL=$(cat "$STATE_FILE" | grep -o '"last_proposal":"[^"]*"' | cut -d'"' -f4)
if [ -n "$LAST_PROPOSAL" ]; then
if [[ "$OSTYPE" == "darwin"* ]]; then
LAST_TS=$(date -j -f "%Y-%m-%dT%H:%M:%S" "${LAST_PROPOSAL%Z}" "+%s" 2>/dev/null || echo "0")
else
LAST_TS=$(date -d "$LAST_PROPOSAL" "+%s" 2>/dev/null || echo "0")
fi
NOW_TS=$(date "+%s")
HOURS_SINCE=$(( (NOW_TS - LAST_TS) / 3600 ))
if [ "$HOURS_SINCE" -lt "$COOLDOWN_HOURS" ]; then
exit 0 # Still in cooldown, skip all git operations
fi
fi
fi
# Check if index.toon exists
if [ ! -f "$INDEX_FILE" ]; then
exit 0
fi
# Get last indexed commit
LAST_REF=$(git log -1 --format="%H" -- "$INDEX_FILE" 2>/dev/null || echo "")
if [ -z "$LAST_REF" ]; then
exit 0
fi
# Calculate change score (only runs if cooldown expired)
SCORE=0
# Get changed files since last index
CHANGED_FILES=$(git diff --name-only "$LAST_REF"..HEAD 2>/dev/null || echo "")
if [ -z "$CHANGED_FILES" ]; then
exit 0
fi
# Count changes
CHANGED_COUNT=$(echo "$CHANGED_FILES" | wc -l | tr -d ' ')
# Extract indexed folders from index.toon
INDEXED_FOLDERS=$(grep -v "^#" "$INDEX_FILE" | cut -d',' -f1 | grep -v "^$" || echo "")
# Check for new top-level folders (not in index)
NEW_FOLDERS=""
for folder in $(echo "$CHANGED_FILES" | cut -d'/' -f1-2 | sort -u | grep -v '^\.' || echo ""); do
if [ -d "$folder" ] && ! echo "$INDEXED_FOLDERS" | grep -q "^$folder$"; then
NEW_FOLDERS="$NEW_FOLDERS $folder"
SCORE=$((SCORE + 3))
fi
done
# Check for deleted indexed folders
DELETED_FOLDERS=""
for folder in $INDEXED_FOLDERS; do
if [ ! -d "$folder" ]; then
DELETED_FOLDERS="$DELETED_FOLDERS $folder"
SCORE=$((SCORE + 3))
fi
done
# Check for changes in indexed folders
CHANGED_INDEXED=""
for folder in $INDEXED_FOLDERS; do
FOLDER_CHANGES=$(echo "$CHANGED_FILES" | grep "^$folder/" | wc -l | tr -d ' ')
if [ "$FOLDER_CHANGES" -gt 0 ]; then
CHANGED_INDEXED="$CHANGED_INDEXED $folder($FOLDER_CHANGES)"
if [ "$FOLDER_CHANGES" -gt 10 ]; then
SCORE=$((SCORE + 2))
elif [ "$FOLDER_CHANGES" -gt 5 ]; then
SCORE=$((SCORE + 1))
fi
fi
done
# Check days since last index
INDEX_DATE=$(git log -1 --format="%ct" -- "$INDEX_FILE")
NOW_DATE=$(date "+%s")
DAYS_SINCE=$(( (NOW_DATE - INDEX_DATE) / 86400 ))
if [ "$DAYS_SINCE" -gt 7 ]; then
SCORE=$((SCORE + 1))
fi
# Output JSON with additionalContext if threshold met
if [ "$SCORE" -ge "$SCORE_THRESHOLD" ]; then
CONTEXT="PROPOSE_REINDEX: index.toon may be outdated (score:$SCORE). $CHANGED_COUNT files changed, last indexed $DAYS_SINCE days ago."
[ -n "$NEW_FOLDERS" ] && CONTEXT="$CONTEXT New folders:$NEW_FOLDERS."
[ -n "$DELETED_FOLDERS" ] && CONTEXT="$CONTEXT Deleted folders:$DELETED_FOLDERS."
[ -n "$CHANGED_INDEXED" ] && CONTEXT="$CONTEXT Changed indexed:$CHANGED_INDEXED."
CONTEXT="$CONTEXT When the user sends their next message, briefly mention that the index may be outdated and ask if they want you to run /redex first, or if you should continue answering their question. Keep it short - one sentence."
# Set cooldown so we don't re-run on every prompt
if [[ "$OSTYPE" == "darwin"* ]]; then
NOW_ISO=$(date -u "+%Y-%m-%dT%H:%M:%SZ")
else
NOW_ISO=$(date -u "+%Y-%m-%dT%H:%M:%SZ")
fi
echo "{\"last_proposal\":\"$NOW_ISO\"}" > "$STATE_FILE"
# Output JSON (additionalContext is injected into Claude's context)
jq -n --arg ctx "$CONTEXT" '{
hookSpecificOutput: {
hookEventName: "SessionStart",
additionalContext: $ctx
}
}'
fiInstall with Tessl CLI
npx tessl i cpoepke/toon-dex@0.3.0